Fake — What It Is, Purpose and How to Use in Testing

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

Fake is a working simplified implementation of a dependency that behaves like a real component but uses in-memory storage or other lightweight mechanisms instead of production infrastructure. Unlike a stub, a fake contains real business logic — sorting, filtering, aggregation — just without external effects. An in-memory database instead of Room or a HashMap instead of SharedPreferences are classic examples. More details in Martin Fowler’s classification of test doubles.

Key Takeaways

  • Fake — a simplified working implementation with real logic but no external dependencies
  • In-memory storage — a fake repository stores data in a HashMap instead of a database
  • Difference from Stub — a stub returns fixed data, a fake contains executable logic
  • Android — InMemoryUserRepository as a Fake for testing ViewModel and UseCase
  • iOS — FakeNetworkSession with URLProtocol and test data instead of a real server

What Is a Fake and Why Is It Needed in Testing?

Fake is a full but lightweight implementation of an interface suitable for testing. The term was introduced by Gerard Meszaros (2007) in the book “xUnit Test Patterns.” Unlike a stub, which returns hardcoded answers, a fake contains executable code: it can sort a list, filter by condition, count records. The only difference from the production implementation is that a fake works with in-memory data and does not perform real I/O operations.

The main advantage is speed. Tests with a fake execute in milliseconds because there is no disk, network, or database access. An in-memory HashMap works 100–1000 times faster than Room or CoreData. At the same time, a fake tests real business logic: sorting, filtering, aggregation — everything that a stub cannot test because a stub only returns what it was told to. A fake gives confidence that the code correctly processes data, rather than just receiving a predetermined answer.

When Fake Is Preferable to Stub

Fake is preferable to Stub — if the component under test performs multiple operations on data (retrieve, filter, sort, save), a stub would require configuring each call individually. A fake contains the logic inside itself — the test simply calls methods and checks the result. At IT Sectr, we use fakes for all repositories in unit tests: a fake repository with a HashMap covers 90% of scenarios without setting up Mockito or MockK.

Fake vs Stub vs Mock: When to Choose What

Selection criterion — determine what the test verifies: state or interaction. If the test verifies state (the result of work) and uses logic — use a fake. If the test only needs input data without logic — a stub is sufficient. If the test verifies that a method was called — use a mock. Mixing test double types in one test complicates understanding and increases fragility.

CriterionFakeStubMock
Has logicYes (simplified)NoNo
SpeedHighMaximumHigh
Behavior verificationIndirectNoYes (verify)
MaintenanceOne class per interfaceConfigure per testConfigure per test
RealismHigh (code works)Low (hardcoded data)Medium
False positive riskLowMediumHigh (fragile tests)

Anti-pattern: Fake that isn’t a fake — a common mistake when a developer calls a fake object that is actually a stub or a mock. If your InMemoryUserRepository contains no logic (filtering, sorting) — it’s not a fake, but a stub with in-memory storage. A fake differs from a stub precisely by the presence of executable logic. If a fake repository simply returns what was put into it and does not process data — use a mock or a stub.

Practical Rule for Choosing a Test Double

Practical recommendation — start with a fake for every repository or service. If a fake exceeds 50 lines — split it into several classes. If a fake is not needed at all (the test only verifies a single scenario with fixed data) — use a stub. If the test verifies that a method was called with specific parameters — use a mock. Don’t optimize the choice in advance: write a fake, and if it turns out to be excessive, replace it with a stub in a specific test.

Creating Fake Objects on Android for Room and Retrofit

Fake repository for Room — a typical Android fake example. The production implementation of UserRepository uses Room DAO with SQLite queries. The fake version stores data in a MutableList or HashMap and implements the same methods: getUser(id), saveUser(user), deleteUser(id). The fake contains search, filtering, and sorting logic — the same as the production repository, but without SQL. This allows testing ViewModel and UseCase without setting up a Room database.

kotlin
class FakeUserRepository : UserRepository {

    private val users = mutableListOf<User>()

    override suspend fun getUser(id: String): User? {
        return users.find { it.id == id }
    }

    override suspend fun saveUser(user: User) {
        val index = users.indexOfFirst { it.id == user.id }
        if (index >= 0) users[index] = user
        else users.add(user)
    }

    override suspend fun search(query: String): List<User> {
        return users.filter {
            it.name.contains(query, ignoreCase = true)
        }
    }
}

Fake for Retrofit API — instead of MockWebServer (which is a stub, not a fake), you can create an ApiService implementation that returns data from an in-memory collection. The difference: MockWebServer intercepts HTTP and returns JSON, while a fake ApiService works at the Kotlin interface level without serialization. A fake is faster (no JSON parsing) and easier to debug (runs in the same process, typed). Suitable for tests where HTTP semantics (status codes, headers) are not important.

FakeSharedPreferences for Fast Tests

— another common scenario. Production SharedPreferences writes to disk via commit/apply. The fake version stores key-value pairs in a HashMap and instantly returns data. It supports the same methods: getString, putString, getInt, putInt, clear. For Jetpack DataStore, the analog is FakeDataStore with in-memory storage. Such fakes speed up tests by tens of times because there are no disk write operations.

Fake Implementations on iOS with In-Memory Storage

Fake in Swift — built through protocols. The production class implements the protocol with real logic (CoreData, URLSession). The fake structure implements the same protocol with in-memory storage and simplified logic. Swift is a language with value semantics, so fake structures are immutable and safe in multithreaded tests. This gives an advantage over Android analogs: there is no need to synchronize access to in-memory data.

swift
protocol UserRepositoryProtocol {
    func getUser(id: String) async -> User?
    func saveUser(user: User) async
}

final class FakeUserRepository: UserRepositoryProtocol {
    private var storage: [String: User] = [:]

    func getUser(id: String) async -> User? {
        return storage[id]
    }

    func saveUser(user: User) async {
        storage[user.id] = user
    }
}

final class UserViewModelTests: XCTestCase {
    func test_save_and_load() async {
        let fake = FakeUserRepository()
        let vm = UserViewModel(repository: fake)
        let user = User(id: "1", name: "Alice")

        await vm.saveUser(user)
        let loaded = await vm.getUser(id: "1")

        XCTAssertEqual(loaded?.name, "Alice")
    }
}

Fake for CoreData — in iOS projects, you can create an in-memory NSPersistentContainer by setting description.type = NSInMemoryStoreType. This is a full CoreData stack, but running in memory. Such a fake allows testing NSFetchRequest, predicates, and sorts without creating an SQLite file. Speed: tests on in-memory CoreData run 5–10 times faster than on the disk-based analog. The downside: you need to set up NSManagedObjectModel each time.

FakeURLProtocol — a URLProtocol subclass for intercepting network requests on iOS. It is registered via URLProtocol.registerClass(fakeProtocol). Internally, it contains an in-memory URL -> Data dictionary and returns data without a real request. The difference from a stub: FakeURLProtocol can check the request body, headers, and return different responses depending on the input data. This is a fake because it contains request routing logic.

Fake Usage Patterns in Mobile Projects

Fake as a Test Fixture — put fake classes in a shared test module (androidTest/sharedTest or TestSupport). All tests in the project use the same InMemoryUserRepository. This eliminates duplication of mock object setup in every test and guarantees uniform behavior. Changing the fake logic updates all tests simultaneously. At IT Sectr, we store fake classes in sharedTest/java/com/itSectr/fake/ and include them via implementation project(:sharedTest).

Fake with preset data — tests often need a repository that already contains some records. Solution: a factory method fakeWithData(vararg items) or a built-in addDefaultData() method. The factory creates a fake, fills it with typical data, and returns a ready-to-use object. This reduces boilerplate in tests: instead of setting up mock calls, the test simply calls FakeUserRepository.withUsers(alice, bob).

Fake with call counting — sometimes you need to verify not just state but also the number of invocations. A fake can contain counters: saveCallCount, getUserCallCount. The test checks the counter after execution. This is a compromise between a pure fake (state verification) and a mock (interaction verification). Counters do not check arguments or call order — only the count. For argument verification, use a mock.

Fake with Callback — for testing async scenarios, a fake can accept a callback on each call: beforeGetUser, afterSaveUser. This allows simulating delays, errors, or checking intermediate states. This approach is useful for testing UI loading states: the fake pauses for 100 ms, and the test verifies that the screen shows a loader. The callback is absent in production — this is purely test functionality.

Frequently Asked Questions

How does a Fake differ from a Stub?

Fake contains working logic — filters, sorts, counts. Stub only returns predetermined answers without logic. If an object has branches (if/else, when) — it’s a fake. If it only contains return values — it’s a stub. A fake is more expensive to maintain but provides more realistic tests.

When can a fake be harmful?

When the fake logic does not match the production logic. For example, FakeUserRepository uses case-sensitive search, while the production version uses case-insensitive search. The test passes, but there’s a bug in reality. Solution: test the fake logic separately or use fakes only for interfaces with simple logic (CRUD operations). For complex logic, write integration tests with a real database.

Is a fake the same as an in-memory database?

In-memory database is one type of fake. Room.inMemoryDatabaseBuilder() creates in-memory SQLite that behaves like a production database. This is a full-fledged fake. But a fake can also be at the repository level (without SQL) and at the network level (FakeApiService). An in-memory database is a special case of a fake where the logic is as close to real as possible.

Can I combine Fake and Mock in one test?

Yes, but with caution. Fake for the repository (data), Mock for AnalyticsTracker (event verification). Separation by layers: fake for the data layer, mock for the analytics/logging layer. Don’t make one object both a fake and a mock — this violates the Single Responsibility Principle and confuses the test.

How do you test the Fake itself?

Test the fake with the same tests as the production implementation. If you have a UserRepositoryTest that verifies save, get, delete — run it twice: with FakeUserRepository and with RealUserRepository. This guarantees that the fake replicates the behavior of the production class. If the fake starts behaving differently — the test will fail on both implementations.

Summary

  • Fake — a working simplified implementation of a dependency with real business logic and in-memory storage
  • Difference from Stub — a fake contains logic (filtering, sorting), a stub only returns data
  • Speed — a fake works 100–1000 times faster than production implementation without I/O operations
  • Android — InMemoryUserRepository, FakeDataStore, in-memory Room via Room.inMemoryDatabaseBuilder
  • iOS — protocol-based fake, in-memory CoreData, FakeURLProtocol for HTTP interception
  • Best practice — put fakes in a shared test module and use them in all project tests
  • Test the fake — run the same tests on the fake and production implementations to verify consistency

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