Test Doubles are substitute objects used in unit testing instead of real dependencies. The term was introduced by Gerard Meszaros in the book “xUnit Test Patterns” (2007) as a general concept for Mock, Stub, Fake, Spy and Dummy. According to Martin Fowler (2024), Test Doubles allow isolating the component under test from its environment, making tests deterministic, fast and independent of external services.
Key Takeaways
Test Doubles is a term from the automotive industry (stunt doubles), transferred to software development. Just as a stunt double replaces an actor in a dangerous scene, a Test Double replaces a real component in a test scenario. This is necessary when the real dependency is unavailable, slow, non-deterministic, or has side effects.
The Test Double concept encompasses five specific types, each solving its own task. The Meszaros typology is canonical and used in all modern testing guides. The difference between types lies in the degree of control and verification: from simple parameter filling (Dummy) to complete call sequence verification (Mock).
The main purpose of Test Doubles is isolation of the module under test. In mobile development, real dependencies include API servers, databases, file systems, device sensors, and system services (LocationManager, Camera, Bluetooth). Using these components directly makes tests slow, brittle, and environment-dependent. According to Google Testing Blog (2023), well-isolated unit tests run in milliseconds, while integration tests run in seconds and minutes.
Gerard Meszaros classification includes five types of Test Doubles, differing in behavior and purpose. Understanding the difference between them is the foundation of competent unit testing.
Dummy is an object that is passed to the method under test but is never used. Dummy is only needed to satisfy the method signature. In Kotlin, this is often null, emptyList(), or an object with stubs. Dummy should not contain any logic — if it is called, the test should fail.
Fake is a simplified but working implementation of an interface. Unlike Mock and Stub, Fake contains real business logic, but in a simplified form. A classic example is InMemoryUserRepository, which stores data in a HashMap instead of a database. Fake is used when you need to test logic that depends on state, but without the overhead of real infrastructure.
| Type | Purpose | Example |
|---|---|---|
| Dummy | Fill a parameter | null, empty object |
| Fake | Working simplified implementation | InMemoryRepository |
| Stub | Return a fixed value | when(api.getUser()).thenReturn(user) |
| Spy | Record calls for verification | verify(spy).save(user) |
| Mock | Verify interaction | verify(mock).sendEmail(email) |
Stub returns predefined values for specific calls. Stub does not check whether it was called — it simply provides data. In Mockito, Stub is created via when(method).thenReturn(value). Stub is ideal for testing when you need a dependency to return a specific value, but the fact of the call itself is not important.
Spy is a wrapper around a real object that records all calls for later verification. Unlike Mock, Spy delegates calls to the real object but allows checking that they occurred. In Mockito, Spy is created via spy(realObject). Spy is useful for partial mocking, when you want to use a real object but verify some calls.
Mock is an object with predefined call expectations. Mock verifies that specific methods were called with specific arguments and in a specific order. Unlike Stub, Mock focuses on behavior verification rather than data return. Mock is the most powerful and most frequently used type of Test Double in mobile development.
The difference between Mock and Stub often causes confusion even among experienced developers. The main difference is in purpose: Stub checks state verification, Mock checks behavior verification.
Stub answers the question: “did the code return the correct result?”. Mock answers the question: “did the code call the correct methods with the correct arguments?”. In mobile development, Stub is used when the result matters (e.g., data from a repository), while Mock is used when side effects matter (e.g., sending email, writing to a database).
// Stub: state verification
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)
// Mock: behavior verification
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }
Practical examples of all five types of Test Doubles in Kotlin using MockK — the most popular mocking library for Android projects.
class InMemoryUserRepository : UserRepository {
private val store = mutableMapOf<String, User>()
override fun save(user: User) {
store[user.email] = user
}
override fun findByEmail(email: String): User? {
return store[email]
}
}
class RegisterUseCaseTest {
private val api = mockk<AuthApi>()
private val repo = spyk(InMemoryUserRepository())
private val useCase = RegisterUseCase(api, repo)
fun `register user successfully`() = runTest {
// Stub: return fixed API response
coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")
val result = useCase.execute("test@test.com")
// Verify: check that the user was saved
verify { repo.save(any()) }
assertTrue(result.isSuccess())
}
}
data class Logger(val appContext: Context, val format: FormatType)
fun `test logger with dummy context`() {
// Dummy: Context is not used inside Logger
val dummyContext = mockk<Context>()
val logger = Logger(dummyContext, FormatType.JSON)
assertEquals(FormatType.JSON, logger.format)
}
The choice of Test Double type depends on what exactly is being tested: state, behavior, or integration. In Android and iOS mobile development, the following recommendations have been established.
When testing ViewModel, use Mock for dependencies that produce side effects (repositories, analytics, navigation) and Stub for dependencies that return data (API clients, ContentProvider). This allows verifying that the ViewModel correctly handles both success and error scenarios.
At the Repository level, prefer Fake (in-memory database implementations) and Stub (fixed API responses). Fake allows testing caching logic and offline mode without setting up SQLite. Stub simulates various HTTP statuses: 200, 404, 500, timeout.
Incorrect use of Test Doubles is one of the most common causes of brittle tests that break with every refactoring.
The most common mistake is mocking everything. If every dependency in a test is replaced with a Mock, the test stops verifying real behavior. Mock should only be used for external dependencies (network, database, file system, system services). Internal application components (Value Object, data class, simple utilities) should not be replaced.
The second mistake is creating a Mock without defining expectations. If a method is called without every / when, Mock returns a default value (null, 0, false). This can lead to false-positive tests, where Mock silently returns null and the test interprets this as correct behavior.
The third mistake is verifying every call of every Mock. Verify should only be used for calls that are critically important from a business logic perspective. Excessive verification makes tests brittle: changing the order of calls in production code breaks tests without changing behavior.
Frequently Asked Questions
Stub returns data and checks state (what was returned), while Mock checks behavior (which methods were called). Stub = “return X”, Mock = “verify that Y was called with argument Z”. In real tests, one object often acts as both Stub and Mock simultaneously.
Fake is preferable to Mock when testing logic that depends on state: caching, offline mode, transactions. Fake (in-memory implementation) allows testing these scenarios without brittle verify calls. Mock is better suited for checking data sending: analytics, push, email.
For Android projects in Kotlin, MockK is recommended. It supports coroutines, suspend functions, sealed classes, and extension functions without additional configuration. For Java projects, Mockito remains the standard — the most popular library with extensive documentation.
To test Kotlin Flow, use the Turbine library together with MockK. Turbine simplifies Flow emission checking: you can verify the order of values, stream completion, and exceptions. Stub for Flow returns flowOf(value), Mock verifies that the Flow was collected.
Yes, but at the API response level, not UI components. Libraries MockWebServer (OkHttp) and WireMock allow mocking HTTP responses in UI tests. The UI components themselves (Compose, SwiftUI Views) should not be replaced — their behavior is tested through screenshot tests and Espresso.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also