Migration from Mocks¶
Migrating from mocking frameworks (MockK, Mockito, Mokkery, Mockative) to Fakt compile-time fakes.
Why Migrate?¶
- KMP compatibility - Mocks rely on JVM reflection; fakes work everywhere
- Refactor-safe tests - Verify outcomes, not implementation details
- Zero runtime cost - Compile-time generation, no reflection overhead
- Simple API - DSL lambdas match your interface signatures
MockK → Fakt¶
Basic Usage¶
MockK:
@Test
fun `GIVEN mock repository WHEN processing user THEN calls getUser`() {
val mock = mockk<UserRepository>()
every { mock.getUser(any()) } returns User("123", "Alice")
val service = UserService(mock)
service.processUser("123")
verify(exactly = 1) { mock.getUser("123") }
}
Fakt:
@Test
fun `GIVEN fake repository WHEN processing user THEN calls getUser`() {
val fake = fakeUserRepository {
getUser { id -> User(id, "Alice") }
}
val service = UserService(fake)
service.processUser("123")
assertEquals(1, fake.getUserCallCount.value)
}
Suspend Functions with Error Handling¶
MockK:
@Test
fun `GIVEN repository failure WHEN fetching data THEN handles error`() = runTest {
val mock = mockk<DataService>()
coEvery { mock.fetchData() } throws NetworkException("Connection timeout")
val viewModel = DataViewModel(mock)
viewModel.loadData()
assertTrue(viewModel.errorState.value is Error.Network)
}
Fakt:
@Test
fun `GIVEN repository failure WHEN fetching data THEN handles error`() = runTest {
val fake = fakeDataService {
fetchData { throw NetworkException("Connection timeout") }
}
val viewModel = DataViewModel(fake)
viewModel.loadData()
assertTrue(viewModel.errorState.value is Error.Network)
}
Key difference: Fakt’s DSL lambda has the same signature as the original interface method. No need to learn separate stubbing APIs (coEvery, every, returns).
Mockito → Fakt¶
Mockito:
@Test
fun `GIVEN repository WHEN saving user THEN returns success`() {
val mock = mock(UserRepository::class.java)
`when`(mock.saveUser(any())).thenReturn(Result.success(Unit))
val service = UserService(mock)
val result = service.createUser("Alice")
assertTrue(result.isSuccess)
verify(mock, times(1)).saveUser(any())
}
Fakt:
@Test
fun `GIVEN repository WHEN saving user THEN returns success`() = runTest {
val fake = fakeUserRepository {
saveUser { user -> Result.success(Unit) }
}
val service = UserService(fake)
val result = service.createUser("Alice")
assertTrue(result.isSuccess)
assertEquals(1, fake.saveUserCallCount.value)
}
Mokkery → Fakt¶
Mokkery:
@Test
fun `GIVEN mock analytics WHEN tracking event THEN records event`() = runTest {
val mock = mock<Analytics>()
everySuspend { mock.track(any()) } returns Unit
val service = AnalyticsService(mock)
service.logUserAction("button_click")
verifySuspend { mock.track("button_click") }
}
Fakt:
@Test
fun `GIVEN fake analytics WHEN tracking event THEN records event`() = runTest {
val trackedEvents = mutableListOf<String>()
val fake = fakeAnalytics {
track { event -> trackedEvents.add(event) }
}
val service = AnalyticsService(fake)
service.logUserAction("button_click")
assertEquals(1, fake.trackCallCount.value)
assertEquals("button_click", trackedEvents.first())
}
Migration notes:
- Replace
mock<T>()withfakeT {} - Replace
everySuspend { }with DSL lambda configuration - Replace
verifySuspend { }with StateFlow call counters - State-based verification (checking
trackedEvents) is more resilient than interaction verification
Mockative → Fakt¶
Mockative:
@Test
fun `GIVEN repository WHEN fetching user THEN returns user`() = runTest {
val mock = mock<UserRepository>()
given(mock).coroutine { getUser("123") }.thenReturn(User("123", "Alice"))
val viewModel = UserViewModel(mock)
viewModel.loadUser("123")
assertEquals("Alice", viewModel.userName.value)
}
Fakt:
@Test
fun `GIVEN repository WHEN fetching user THEN returns user`() = runTest {
val fake = fakeUserRepository {
getUser { id -> User(id, "Alice") }
}
val viewModel = UserViewModel(fake)
viewModel.loadUser("123")
assertEquals("Alice", viewModel.userName.value)
assertEquals(1, fake.getUserCallCount.value)
}
Generic Repositories¶
MockK:
@Test
fun `GIVEN generic repository WHEN saving item THEN returns success`() {
val mock = mockk<Repository<User>>()
every { mock.save(any()) } returns Result.success(Unit)
val service = CrudService(mock)
service.createUser(User("123", "Alice"))
verify { mock.save(any<User>()) }
}
Fakt:
@Test
fun `GIVEN generic repository WHEN saving item THEN returns success`() {
val fake = fakeRepository<User> {
save { item -> Result.success(Unit) }
}
val service = CrudService(fake)
service.createUser(User("123", "Alice"))
assertEquals(1, fake.saveCallCount.value)
}
Type safety: Fakt preserves generic type parameters. The DSL lambda receives item: User, not item: Any.
Next Steps¶
- Testing Patterns - Best practices for fake-based testing
- Usage Guide - Core Fakt patterns and examples
- Performance - Build time impact and optimization