I wanted to re-implement `InstancePerLeaf` as an e...
# kotest
l
I wanted to re-implement
InstancePerLeaf
as an extension/listener. Do you have any hints how I could go about it? More details in 🧵
We are testing viewmodels. Viewmodels are essentially state machines. Not all state transitions are allowed.
E.g. we have cases where the viewmodel should emit state where it shows a dialog. Then there should be two child tests, "dialog confirmed" and "dialog dismissed". Then every of these cases can also have child tests etc.
The point is, every one of the resulting leaf tests should be completely independent of "parallel" leaf tests (siblings). But should take into account the whole ancestry path that leads to the leaf tests, i.e. all surrounding container tests.
That means at the last container test I have to somehow cache the state of system-under-test (the viewmodel) and then pass it to initialize every final leaf test. However, I also have to do it at every surrounding container test level, and recursively until the first container test. I could omit the caching for the container tests which only contain one child.
Example test
Copy code
"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" {
                viewModel.enterCode(correctCode)
                viewModel.uiState shouldBe Success
            }
        }
    }

    "when the customer scanned the correct code then show success" {
        viewModel.scanCode(correctCode)
        viewModel.uiState shouldBe Success
    }
}
The point is, e.g. calling
scanCode()
in
Success
state should not be allowed.
Or calling
onManualInputSelected()
should also only be allowed in
ErrorDialog
state, not in
EmptyState
.
So you cannot just run the tests through from top to bottom, keeping the old state. The state has to be reset to the level of the containing test, before entering next child test.
So I think the solution would be: 1. At the end of every container test cache the system-under-test (viewmodel) 2. inject it at the beginning of the contained test
For this I need to know exactly at which node of the test tree I currently am. I.e. for this structure:
Copy code
class MyTest : FreeSpec({
    
    "a" - {
        sut.doSomething()

        "b" - {
             sut.doSomethingElse()

            "c" {
                sut.doSomethingMore()
            }

            "d" {
                sut.doSomething()
            } 
        }

        "e" {
            sut.doSomethingElseYet()
        }
    }
})
I need to inject
sut
in state from the end of
"a"
to
"b"
and
"e"
. I need to inject
sut
in the state from the end of
"b"
to
"c"
and
"d"
.
So I need a map of unambiguous container test node coordinates to
sut
instances. And I need to know which test node's child the current test is (what its container is).
Note for simplicity that we don't allow code to be executed between siblings (e.g.
"c"
and
"d"
or
"b"
and
"e"
).
Is there a Listener/Extension which gives access to the current & containing test coordinates?
Or would something like this have to be implemented deeply in the framework itself?
Also I need a way to reference the injected
sut
somehow in the test body.