Test Doubles — Types of Substitutes and Usage

Author: IT Sectr Published: 2026-04-10 Reading time: 9 min

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 — a general term for all types of substitute objects in testing
  • Mock verifies interaction: which methods were called and with what arguments
  • Stub returns predefined values without verifying calls
  • Fake — a simplified working implementation (e.g., in-memory database)
  • Spy records calls for later verification, Dummy fills parameters

What Are Test Doubles?

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).

Why Test Doubles Are Needed

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.

Five Types of Test Doubles

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

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

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.

TypePurposeExample
DummyFill a parameternull, empty object
FakeWorking simplified implementationInMemoryRepository
StubReturn a fixed valuewhen(api.getUser()).thenReturn(user)
SpyRecord calls for verificationverify(spy).save(user)
MockVerify interactionverify(mock).sendEmail(email)

Stub

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

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

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.

Mock vs Stub: Key Differences

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).

kotlin
// 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") }

Test Doubles Examples in Kotlin

Practical examples of all five types of Test Doubles in Kotlin using MockK — the most popular mocking library for Android projects.

Fake: InMemoryUserRepository

kotlin
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]
    }
}

Stub + Mock: UseCase test

kotlin
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())
    }
}

Dummy: test with unused parameter

kotlin
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)
}

When to Use Which Type in Mobile Development

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.

For ViewModel and UseCase

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.

For Repository and Data Layer

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.

  • Business logic unit tests — Mock for all external dependencies, Dummy for unused parameters
  • Integration tests — Fake instead of Mock (verify that components work together)
  • UI tests — Stub for API responses (via MockWebServer or WireMock)
  • Caching tests — Fake for database (in-memory instead of Room/SQLite)
  • Asynchrony tests — Mock with coroutine support (MockK + Turbine for Flow)

Common Mistakes When Using Substitutes

Incorrect use of Test Doubles is one of the most common causes of brittle tests that break with every refactoring.

Over-mocking: excessive use of Mock

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.

Under-specification: insufficient specification

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.

Over-verification: excessive verification

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

What is the difference between Mock and Stub?

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.

When to use Fake instead of Mock?

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.

Which Test Doubles library is best for Android?

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.

How to test Kotlin Flow with Test Doubles?

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.

Is it acceptable to use Test Doubles in UI tests?

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

  • Test Doubles — a general term for five types of substitute objects: Mock, Stub, Fake, Spy, Dummy
  • Mock checks behavior (verify), Stub returns data (thenReturn), Fake works as a simplified real implementation
  • Spy wraps a real object and records calls, Dummy fills unused parameters
  • Gerard Meszaros typology is the canonical classification used in all modern mocking frameworks
  • For Kotlin projects, MockK is recommended, for Java — Mockito, for iOS — Cuckoo or OHHTTPStubs
  • Common mistakes: over-mocking (replacing everything), under-specification (undefined expectations), over-verification (excessive verify)
  • Fake is preferable to Mock when testing stateful logic — caching, offline mode, and transactions

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.

Discuss the project

Read also