Mocking and Kotest

Kotest itself has no mock features. However, you can plug-in your favourite mocking library with ease!

Let's take for example mockk:

class MyTest : FunSpec({
val repository = mockk<MyRepository>()
val target = MyService(repository)
test("Saves to repository") {
every { repository.save(any()) } just Runs
target.save(MyDataClass("a"))
verify(exactly = 1) { repository.save(MyDataClass("a")) }
}
})

This example works as expected, but what if we add more tests that use that mockk?

class MyTest : FunSpec({
val repository = mockk<MyRepository>()
val target = MyService(repository)
test("Saves to repository") {
every { repository.save(any()) } just Runs
target.save(MyDataClass("a"))
verify(exactly = 1) { repository.save(MyDataClass("a")) }
}
test("Saves to repository as well") {
every { repository.save(any()) } just Runs
target.save(MyDataClass("a"))
verify(exactly = 1) { repository.save(MyDataClass("a")) }
}
})

The above snippet will cause an exception!

2 matching calls found, but needs at least 1 and at most 1 calls

This will happen because the mocks are not restarted between invocations. By default, Kotest isolates tests by creating a single instance of the spec for all the tests to run.

This leads to mocks being reused. But how can we fix this?

Option 1 - setup mocks before tests#

class MyTest : FunSpec({
lateinit var repository: MyRepository
lateinit var target: MyService
beforeTest {
repository = mockk()
target = MyService(repository)
}
test("Saves to repository") {
// ...
}
test("Saves to repository as well") {
// ...
}
})

Option 2 - reset mocks after tests#

class MyTest : FunSpec({
val repository = mockk<MyRepository>()
val target = MyService(repository)
afterTest {
clearMocks(repository)
}
test("Saves to repository") {
// ...
}
test("Saves to repository as well") {
// ...
}
})

Positioning the listeners#

As for any function that is executed inside the Spec definition, you can place listeners at the end

class MyTest : FunSpec({
val repository = mockk<MyRepository>()
val target = MyService(repository)
test("Saves to repository") {
// ...
}
test("Saves to repository as well") {
// ...
}
afterTest {
clearMocks(repository) // <---- End of file, better readability
}
})

Option 3 - Tweak the IsolationMode#

Depending on the usage, playing with the IsolationMode for a given Spec might be a good option as well. Head over to isolation mode documentation if you want to understand it better.

class MyTest : FunSpec({
val repository = mockk<MyRepository>()
val target = MyService(repository)
test("Saves to repository") {
// ...
}
test("Saves to repository as well") {
// ...
}
isolation = IsolationMode.InstancePerTest
})