A fake is a small working implementation (Testing with Fake Dependencies's FakeBookRepository); a mock is an object whose answers you script and whose calls you verify. Mocks shine when the point is the interaction ("did Retry call the repository again?"). MockK 5,760 (https://github.com/mockk/mockk 5,760 ) (Apache 2.0, io.mockk:mockk:1.14.11) is the Kotlin-first choice, with coEvery and coVerify for suspend functions:
@Test fun retryAsksTheRepositoryAgain() = runTest {
val repo = mockk<BookRepository>()
every { repo.observeBooks() } returns emptyFlow()
every { repo.observeStatus() } returns emptyFlow()
coEvery { repo.loadBooks() } throws IOException("offline") andThen testBooks
val vm = CatalogViewModel(repo)
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { vm.books.collect {} }
assertEquals(LoadState.Error("offline"), vm.books.value)
vm.refresh()
assertEquals(LoadState.Success(testBooks), vm.books.value)
coVerify(exactly = 2) { repo.loadBooks() }
}A strict mockk() throws on any unscripted call, so even the two observe functions, which have default bodies in the interface, need answers; mockk(relaxed = true) returns empty defaults instead. The test passed in 3.25 seconds against 0.02 for the fake-based version, and stderr said why: WARNING: A Java agent has been loaded dynamically (.../byte-buddy-agent-1.18.2.jar). MockK rewrites bytecode through a ByteBuddy agent attached at run time, a one-off cost per test JVM. Mockito 311,471 with mockito-kotlin is the main alternative. Prefer fakes for your own interfaces and mocks for boundaries you don't own, and never mock data classes: build real Book objects, as testBooks does.