Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for nested tests on JS target (again) #3141

Closed
ptitjes opened this issue Aug 8, 2022 · 10 comments · May be fixed by #3913
Closed

Support for nested tests on JS target (again) #3141

ptitjes opened this issue Aug 8, 2022 · 10 comments · May be fixed by #3913
Labels
enhancement ✨ Suggestions for adding new features or improving existing ones. stale 🏚️ Issues that were lost to time and are no longer up-to-date

Comments

@ptitjes
Copy link

ptitjes commented Aug 8, 2022

Nested tests on JS target were working in 5.0.0. However, it seems to be broken in newer versions.

One of the good things of Kotest is the ability to have nested tests. However, nested tests not working on the JS target means that you're stuck to not using this feature as soon as you have a multiplatform Kotlin project that also targets JS (which is supposed to be a lot of projects). This is a feature that is tremendously useful to organize tests in big projects.

I have seen here that @sksamuel says:

On JS it's not possible without changing Kotest in breaking ways.

However, nested tests are supported in (almost) all test runners in the JS world. So it would seem that the problem comes from Kotest design.

In addition, in some Slack discussion that I can't find again, @sksamuel said that the problem comes from intermediate (container) levels use suspending lambda. I am wondering why this is, as from my point of view, suspending lambdas only make sense for the content of individual tests and before/after hooks.

I also have seen that there is a work-in-progress PR on this subject.

@sksamuel could we please discuss what needs to (breaking) change in Kotest to support this?

@ptitjes ptitjes added the enhancement ✨ Suggestions for adding new features or improving existing ones. label Aug 8, 2022
@sksamuel
Copy link
Member

sksamuel commented Aug 25, 2022

The Javascript test runners don't support promises at the outer layer.

Eg in mocha,

describe("foo") {  <---- does not support promises
   test("bar") { <--- supports promises
    }
}

Therefore we cannot use coroutines anywhere but the final scope. Since kotest supports coroutines at every layer, the only way to make it work in JS land is to support flat tests only. Unless you can find a JS test runner that supports promises at every level of nesting.

The alternative would be to change kotest to not support coroutines anywhere but the final scope, and that would be a) a huge breaking change and b) reduce the functionality for anyone in JVM land.

@sksamuel
Copy link
Member

It looks like https://github.com/substack/tape might supported nested async tests, so we could explore that for Kotest 6.0

@ptitjes
Copy link
Author

ptitjes commented Aug 31, 2022

@sksamuel I can't find anything in Tape's documentation about nested async tests (that also have the top levels async). Would you mind pointing something to me? I am not against trying to make an implementation.

Also, I understand that it would indeed be a huge breaking change. However, I am not sure to understand the interest of having the top levels being async too.
The only thing I can see would be to use asynchronous retrieval from a database to load metadata to build test cases. Is that it?

@sksamuel
Copy link
Member

sksamuel commented Sep 1, 2022

Aside from whether it's a good idea or not to have async parent tests, changing it now would break thousands of installs.

In tape it looks like a top level test comes with a callback t which can then fire more tests https://github.com/substack/tape#ttestname-opts-cb

@stale
Copy link

stale bot commented Oct 1, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale 🏚️ Issues that were lost to time and are no longer up-to-date label Oct 1, 2022
@BenWoodworth
Copy link

BenWoodworth commented Oct 14, 2022

Very much a hack, but I got this working which suits my needs, and should be easy to migrate over if/when nested JS test support is added: FlatSpec.kt

Obligatory disclaimer: use with caution. No guarantee it'll work in future releases, as using FunSpec in this way is almost certainly not supported. That being said though, feel free to use it, just leave the block comment for any future travelers who may be (mis)fortunate enough to stumble upon it :)

image

@stale stale bot removed the stale 🏚️ Issues that were lost to time and are no longer up-to-date label Oct 14, 2022
aSemy added a commit to adamko-dev/kotlinx-serialization-typescript-generator that referenced this issue Nov 9, 2022
aSemy added a commit to adamko-dev/kotlinx-serialization-typescript-generator that referenced this issue Nov 9, 2022
* bump Gradle 7.5.1

* create KxsTsGenBuildSettings, so TSCompile tests can be enabled/disabled with a flag

* create github workflows

* kotlin.mpp.stability.nowarn=true

* bump kotlinx-kover

* bump kotlin 1.7.21

* bump gradle-git-versioning-plugin

* tweak gradle props

* update property name kxstsgen_enableTsCompileTests

* increase logging, disable fail-fast

* binaries.executable() -> browser()

* add rootProject.name to buildSrc Gradle settings

* bump Kotlin language level to 1.7, standardise Jvm target to 11

* rm java-library from kotlin-mpp

* setup java-test-fixtures, change 'example' to be in 'main' source set

* only enable NPM setup when TSCompile is enabled

* update kotlin-process

* disable kotest plugin kotest/kotest#3141

* use embeddedKotlinVersion shortcut

* improve ts compile testing

* combine TS compile tests in matrix
@stale
Copy link

stale bot commented Nov 13, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale 🏚️ Issues that were lost to time and are no longer up-to-date label Nov 13, 2022
@stale stale bot closed this as completed Dec 21, 2022
@aSemy
Copy link
Contributor

aSemy commented Feb 11, 2023

Maybe using kotlin.test could help with dynamic test registration on JS?

https://discuss.kotlinlang.org/t/kotlin-test-js-specifying-test-without-annotation/11280

piacenti Jan '19

I found a way to create the tests without the annotation and they work.
Very quick and dirty:

import kotlin.test.Test
import kotlin.test.assertTrue

@JsModule("kotlin-test")
@JsNonModule
external val kTest: dynamic

class TestJs {

  @Test
  fun blah() {
    kTest.kotlin.test.suite("package test", false) {
      kTest.kotlin.test.suite("suit test", false) {
        kTest.kotlin.test.test("test", false) {
          assertTrue(true)
        }
      }
    }
  }
}

But not perfect yet since karma seems to have the counts a bit messed up. I had 4 test before adding this code and then it went to 6 tests out of 4.

HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 0 of 4 SUCCESS (0 secs / 0 secs)
HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 1 of 4 SUCCESS (0 secs / 0.005 secs)
HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 2 of 4 SUCCESS (0 secs / 0.006 secs)
HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 3 of 4 SUCCESS (0 secs / 0.009 secs)
HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 4 of 4 SUCCESS (0 secs / 0.01 secs)
HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 5 of 4 SUCCESS (0 secs / 0.01 secs)
HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 6 of 4 SUCCESS (0 secs / 0.01 secs)
HeadlessChrome 72.0.3617 (Windows 10.0.0): Executed 6 of 4 SUCCESS (0.105 secs / 0.01 secs)

Making the test class an object gets rid of the additional duplicate tests generated by the code above

I've fleshed this out and created a working example on Kotlin 1.8.10, JS IR browser

import kotlin.test.Test
import kotlin.test.assertTrue

@JsModule("kotlin-test")
@JsNonModule
external val kTest: dynamic

class TestJs {

  // some dummy service that we'll test
  class UsernameValidator {
    fun isValid(username: String?) = username!!.isNotBlank()
  }

  @Test
  fun passwordValidationTests() {
    // create an outer context
    context("Given a password validator") {
      val usernameValidator = UsernameValidator()

      // nest some inner contexts + tests
      context("When username is very long") {
        val username = "very long username ".repeat(100)
        test("Then expect validation passes") {
          assertTrue { usernameValidator.isValid(username) }
        }
      }
      context("When username is not blank") {
        val username = "foo bar"
        test("Then expect validation passes") {
          assertTrue { usernameValidator.isValid(username) }
        }
      }

      // tests + contexts can be dynamically added
      mapOf(
        "blank" to "   ",
        "null" to null,
      ).forEach { (failureReason, username) ->
        context("When username is $failureReason") {
          test("Then expect validation fails") {
            assertTrue { usernameValidator.isValid(username) }
          }
        }
      }
    }
  }
}

/** Test context - for nesting tests */
fun context(name: String, ignored: Boolean = false, contextBlock: () -> Unit) {
  kTest.kotlin.test.suite(name, ignored) {
    contextBlock()
  }
}

/** Execute a test */
fun test(name: String, ignored: Boolean = false, testBlock: () -> Unit) {
  kTest.kotlin.test.test(name, ignored) {
    testBlock()
  }
}

Example result:

image

So the tests aren't nested, but kotlin.test will prepend the contexts to the test name.

@eygraber
Copy link
Contributor

eygraber commented Sep 8, 2023

Is there any plan to support this anytime soon? I'm porting a JVM library to KMP and running into this issue when adding a js target. I'd hate to move away from kotest and there are hundreds of tests that are affected.

@sksamuel
Copy link
Member

It's going to be added when we can build on top of compiler plugins stably. I am hoping that will be after Kotlin 2.0 is released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement ✨ Suggestions for adding new features or improving existing ones. stale 🏚️ Issues that were lost to time and are no longer up-to-date
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants