huehnerlady
09/02/2025, 6:56 AMOliver.O
09/02/2025, 10:28 AMhuehnerlady
09/02/2025, 10:33 AMbeforeTest
On top of that if we have an object that gets manipulated by the test, we have to change val to var and instantiate it in beforeTest
lateinit var something: MyObject
beforeTest {
something = MyObject("will be changed in test)
}
in comparison to
val something = MyObject("will be changed in test)huehnerlady
09/02/2025, 10:34 AMOliver.O
09/02/2025, 10:37 AMOliver.O
09/02/2025, 2:05 PMShervin
09/02/2025, 2:57 PMOliver.O
09/02/2025, 3:04 PMTasuku Nakagawa
09/02/2025, 3:14 PMCould someone please motivate why this has to go?you can find the reason from this commit message; Kotest team seemed necessary to rework existing Kotest engines and isolation modes to achieve more robust concurrency with simple implementation https://github.com/kotest/kotest/commit/65263415b83087f34b938d7b462139d8c8b02bc5#diff-679342fbe030cf57a6512c2c94d[…]cd0b688d341b9fa84b14a496dcec7c4e
Shervin
09/03/2025, 7:22 AMOliver.O
09/03/2025, 9:08 AMInstancePerLeaf case.
The general principle is: If your test needs a fresh context for each leaf or test container, create the context on the required level (in my case, it was the leaf) and create an extension function that "injects" the context into your test case (suspend Context.() -> Unit). In my above case, I had created a new variant for the test function that does so.
Having said that, I'm not aware of a clean migration solution that would work in all cases. I always found the Kotest mechanism to re-instantiate specs too complicated to use safely, so I never did.
Maybe something like this work might for you:
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.core.spec.style.scopes.BehaviorSpecWhenContainerScope
import io.kotest.matchers.shouldBe
class MyBehaviorSuite : BehaviorSpec() {
private class MyClass(var myProperty: Int = 1)
private class MyContext {
val myObject: MyClass = MyClass()
}
init {
given("something") {
`when`("it matters") {
then("one", MyContext()) {
myObject.myProperty shouldBe 1
myObject.myProperty += 10
myObject.myProperty shouldBe 11
}
then("two", MyContext()) {
myObject.myProperty shouldBe 1
myObject.myProperty += 20
myObject.myProperty shouldBe 21
}
}
}
}
}
suspend fun <Context> BehaviorSpecWhenContainerScope.then(
name: String,
context: Context,
action: suspend Context.() -> Unit
) = then(name) {
context.action()
}huehnerlady
09/03/2025, 1:30 PMit), and not have to think about what I need to do in order to achieve the instance per it...
I appreciate that you might want to remove the current instancePerLeaf/Test, but maybe there could be a new isolationMode that covers the complete isolation of tests, so people do not have to deal with test bleeding (which is the case at the moment)
The context solution works, my solution works too, but it does make the tests more complex and less readable than the InstancePerLeaf does/didOliver.O
09/03/2025, 1:43 PMhuehnerlady
09/03/2025, 1:51 PMOliver.O
09/03/2025, 2:25 PMOliver.O
09/05/2025, 6:54 PMLukasz Kalnik
11/10/2025, 2:59 PMmyObject would be modified in the given and when blocks as well?
I want the modifications from given / when to flow into the nested then blocks, but the modifications inside the first then should not leak to the other then instance (and similarly for parallel when etc.).Oliver.O
11/10/2025, 3:09 PM{
object {
var a: Int = 1
val b: Int = 2
}
}.generatingFixtureFor {
test("one") {
it.a++ //and we can even modify them in one test
println("a=${it.a}, b=${it.b}") //a=2, b=2
}
test("two") {
//without affecting the other!
println("a=${it.a}, b=${it.b}") //a=1, b=2
}
}
Could this inspire you to make something like this work with Kotest?Lukasz Kalnik
11/10/2025, 3:10 PMLukasz Kalnik
11/10/2025, 3:10 PMLukasz Kalnik
11/10/2025, 3:18 PM"given a viewmodel was created" - {
val viewModel = MyViewModel()
viewModel.uiState shouldBe EmptyState
"when the customer scanned the wrong code then show error dialog" - {
viewModel.scanCode(wrongCode)
viewModel.uiState shouldBe ErrorDialog
"when the customer dismissed the dialog then close it" {
viewModel.onDismissErrorDialog()
viewModel.uiState shouldBe EmptyState
}
"when customer selected manual input then show manual input dialog" - {
viewModel.onManualInputSelected()
viewModel.uiState shouldBe ManualInput
"when the customer entered correct code manually then show success" {
// ...
}
}
}
"when the customer scanned the correct code then show success" {
viewModel.scanCode(correctCode)
viewModel.uiState shouldBe Success
}
}Lukasz Kalnik
11/10/2025, 3:19 PMOliver.O
11/10/2025, 3:26 PMLukasz Kalnik
11/10/2025, 3:27 PMLukasz Kalnik
11/10/2025, 3:28 PMOliver.O
11/10/2025, 3:35 PMOliver.O
11/10/2025, 3:36 PMLukasz Kalnik
11/10/2025, 3:36 PM