Hi, We are currently migrating to kotest 6. We sa...
# kotest
h
Hi, We are currently migrating to kotest 6. We saw that the InstancePerLeaf is deprecated. Could someone please motivate why this has to go? It says there are some edge cases that are not defined. Which edge cases are those? Would it not be possible to document those edge cases - or resolve them - and leave that setting in place? We tried to move InstancePerLeaf -> InstancePerRoot, but now the tests are really bloated up and a lot harder to read.... So our idea is to stay on InstancePerLeaf and hope that maybe you change your mind? 😓
o
In order to explore options: Could you share an example where your migration led to bloated code?
h
well we always have to wrap everything in
beforeTest
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
Copy code
lateinit var something: MyObject

beforeTest {
something = MyObject("will be changed in test)
}
in comparison to
Copy code
val something = MyObject("will be changed in test)
I try to find the time to create a sample project, but thought this is a first quick response 🙂
o
I get the idea. To think about it and not miss the point, a single test source (could be faked) in its shape before and after the migration would be just good enough.
I have prepared a Gist with two alternatives you could use. Does that help?
s
@Oliver.O would you be able to share a gist which covers a test with instance per leaf mode with what it is supposed to be after migrating it to Kotest v6 please ? that can be very helpful for us, to estimate the effort needed to migrate out tests which we have thousands of them.
o
Isn’t that what my above Gist shows? The first source requires Kotest < 6.0 while the other two variants should work with Kotest 6.0.
t
Could 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
s
@Oliver.O from what I see the gist is about InstancePerTest -> WithoutIsolation, I'm looking for InstancePerLeaf -> WithoutIsolation, specially in Complex test with BehaviourSpec
o
My above Gist was so simple that it actually fit the
InstancePerLeaf
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:
Copy code
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()
}
h
For me I would like to just have a new instance per Leaf/test (We use describe spec, so it would be per
it
), 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/did
o
I wasn't involved in recent framework changes. Regarding readability/complexity then: • Yes, formerly you could rely on the framework re-instantiating your class, which was convenient (and easy to reason about in simple cases). • The downside is that the magic behind the scenes is hard to reason about (and control), especially if there are multiple container levels involved, and more so if these container levels themselves contain assertions. Multiple levels of initialization must re-run just to provide a clean setup for the next test. That's complex. On the other hand, with the newer solutions (like mine): • You need to declare a separate context object, which is two additional lines of code (the custom test function can be re-used). • The tests themselves do not change, so the main test code does not suffer from bloat. • Control flow is explicit and simple, you can see exactly when the context initializes. No magic, you always know where to set a breakpoint if something is suspect. Does that make sense?
h
I do understand. From my point of view - a users point of view, is it a loss of convenience though, hence we hoped it would be readded in a different way again how do you add mocked services, like eg. with mockk in this gist?
o
I don't use mocks, I use fakes where necessary.
😅 1
I guess it's about time to write an article "Say goodbye to dinosaur testing patterns which no longer fit today's Kotlin ecosystem". For mocks, some insights can be found here: https://amzn.github.io/app-platform/testing/#mocks
😅 1