Fakes, mocks and stubs

Testing Junior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Pick the fix

A repository test mocks DAO calls one by one and breaks whenever the implementation changes, even when results are the same. What's better?

Options
Hint 1 of 1

What should the test check: calls or results?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Use a fake DAO (or an in-memory Room database) and assert on results

Correct. The test checks behaviour, not the order of internal calls.

Why not the others?
  • B Mock more methods. Even more brittle.
  • C Use reflection to call private methods. Tests implementation details.
  • D Skip repository tests. Loses coverage.

30-second answer

A stub returns canned answers. A mock records calls so you can verify interactions, such as with MockK. A fake is a small working implementation, such as an in-memory repository backed by a MutableStateFlow. Google's guidance prefers fakes for your own interfaces: they're simple, reusable and test behaviour, while mocks tie tests to implementation details.

Practise saying it · 30 s. No recording, just a timer

Explain fakes, mocks and stubs.

Say your answer out loud, then check which of these points you covered:

  • Stub: canned answers
  • Mock: verify calls
  • Fake: small working implementation
  • Prefer fakes

Deep dive

Fakes live in a shared test module so every test uses the same one.

Mocks are useful for verifying calls to things you can't fake easily, like analytics.

Over-mocking makes refactors break tests even when behaviour is unchanged.

A fake must behave like the real thing, including errors you can trigger.

A fake repository Compile-checked · Kotlin 2.4.21
interface NotesRepository {
    val notes: Flow<List<String>>
    suspend fun add(note: String)
}

class FakeNotesRepository : NotesRepository {
    private val state = MutableStateFlow<List<String>>(emptyList())
    var failNext = false
    override val notes: Flow<List<String>> = state
    override suspend fun add(note: String) {
        if (failNext) { failNext = false; throw java.io.IOException("offline") }
        state.update { it + note }
    }
}

Common mistakes

  • Mocking every dependency.
  • Verifying internal call order instead of results.
  • Fakes that never fail, so error paths aren't tested.

Senior take & follow-ups

Define each in one line, then say you prefer fakes and why.

Follow-up questions

Think of your answer first, then open the hint.

  1. When is a mock better than a fake?

    Show hint

    When you need to verify an interaction like 'analytics event sent'.

  2. How do you test error paths with a fake?

    Show hint

    Give the fake a switch that makes the next call throw.

Compare on iOS: Test doubles: stubs, spies, mocks and fakes Same vocabulary and the same preference for fakes.

Suggest a fix if something here is wrong or out of date.