Stub (stub, test double) — a test object that returns predefined responses to method calls instead of a real implementation. In mobile development, stubs isolate the tested module from network requests, databases, and file systems, allowing logic verification without environment setup. Unlike mock, stub does not verify behavior — it only provides data. For more details, see Martin Fowler's article on test doubles.
Key Takeaways
Stub is a test double object that replaces a real dependency in a test and returns predefined values for specific calls. The term was introduced in Gerard Meszaros's classification (2007) in the book “xUnit Test Patterns”. Stub belongs to the category of test doubles — objects that substitute real components during testing. The main purpose of a stub is to provide the tested unit with predictable data, removing uncertainty from external systems.
How it works — the test configures the stub before execution: “when getUsers() method is called, return this list of users”. Stub contains no business logic, does not verify call order, and does not record call history. It simply stands in place of the real component and returns what it was told. In Android testing context, this means: the OkHttp client does not make a real request to the server, but receives a response from MockWebServer configured as a stub.
When to use — stubs are optimal for testing the UI layer (ViewModel, Presenter) and business logic (UseCase, Interactor), where you need to verify reactions to specific data: empty list, server returned error 500, token expired. Any case where the test requires a specific input state is a task for stub. Each test scenario gets its own stub configuration, making tests readable and predictable.
Gerard Meszaros (2007) in the book “xUnit Test Patterns” identified five types of test doubles: dummy, stub, spy, mock, fake. Each type serves its purpose. Dummy — passed but not used. Stub — returns data. Spy — records calls. Mock — verifies behavior. Fake — contains simplified logic. Understanding this classification helps developers choose the right tool for each test scenario.
Network requests — the most common scenario for using stubs. The application makes HTTP calls to the API, and the test needs to verify reactions to different responses: successful JSON, 401 error (unauthorized), timeout, empty array. MockWebServer (OkHttp) on Android and URLProtocol (iOS) act as stubs, returning predefined HTTP responses without a real server connection. This speeds up tests from seconds to milliseconds.
Database — Room (Android) and CoreData (iOS) have in-memory variants, but setting them up still takes time. Stub instead of the repository returns pre-prepared Entity lists without touching the database. This is especially effective for testing the ViewModel, where you need to verify sorting, filtering, or data transformation. The test runs in milliseconds regardless of data volume.
System services — LocationManager, SensorManager, SharedPreferences require a real device or emulator. Stub for LocationProvider returns predefined coordinates, for SensorManager — fixed accelerometer values. On iOS, the equivalent is CLLocationManager with a test delegate implementation. Without stubs, such tests require a physical device with specific conditions.
File system and cache — image loading, response caching, configuration file handling — all these operations depend on disk state. Stub for FileManager or ImageCache returns success/error without reading real files. This eliminates false test failures due to path mismatches or access rights on different developer machines.
Division of responsibility — three types of test doubles solve different tasks. Stub: “give me data”. Mock: “verify that I was called”. Fake: “I work like the real one, just simpler”. The difference is critical for test readability: if a test uses mock where stub is needed, it is overloaded with verify calls unrelated to the scenario being tested.
| Characteristic | Stub | Mock | Fake |
|---|---|---|---|
| Purpose | Provide data | Verify interaction | Simplified implementation |
| Logic | None | None | Yes (but simplified) |
| Verification | None | Yes (verify) | Indirect (via state) |
| Flexibility | Low — hardcoded responses | Medium | High — logic adapts |
| Speed | Maximum | High | Medium |
| Example | MockWebServer returns JSON | Mockito.verify(repository).save() | InMemoryRepository with HashMap |
Practical rule — if the test is checking what data the component under test received — use stub. If the test is checking whether the component called a dependency method with the right arguments — use mock. If you simply want to replace a database with a hash table — that is a fake. Mixing types in one test makes it brittle: when the implementation changes, you will have to rewrite both stub and verify logic.
Stub with verify — a common mistake where a developer sets up a stub and then adds verify(stub).method(). Stub by definition should not be verified — there is mock for verification. If you need to check that a method was called with specific arguments, use Mockito.mock() instead of Mockito.stub(). This separation keeps the test intent clear for other developers.
MockWebServer — an OkHttp library for creating HTTP stubs on Android and JVM. It starts a local HTTP server on a specified port that intercepts OkHttp client requests and returns predefined responses. Setup takes three lines: create the server, enqueue the response, start it. The test can sequentially enqueue multiple responses for scenarios with pagination or retries.
class UserRepositoryTest {
private val server = MockWebServer()
fun setup() {
server.start(8080)
val client = OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build()
}
fun test_user_list_success() {
val json = "[{\"id\":1,\"name\":\"Alice\"}]"
server.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200)
)
val result = repository.getUsers()
assertEquals(1, result.size)
}
fun teardown() {
server.shutdown()
}
}
MockK — an alternative to Mockito for Kotlin with first-class support for coroutines, extension functions, and sealed classes. Stubs in MockK are created via coEvery (for suspend functions) and every (for regular ones). Unlike MockWebServer, MockK stubs individual dependency methods rather than the entire HTTP layer. This is convenient for unit tests of UseCase or Interactor, where dependencies are repository abstractions.
interface UserRepository {
suspend fun getUsers(): List<User>
}
class GetUsersUseCaseTest {
private val repo = mockk<UserRepository>()
private val useCase = GetUsersUseCase(repo)
fun test_empty_list() = runTest {
coEvery { repo.getUsers() } returns emptyList()
val result = useCase.invoke()
assertTrue(result.isEmpty())
coVerify(exactly = 1) { repo.getUsers() }
}
}
Best practice — for integration tests use MockWebServer (intercepts real HTTP), for unit tests use MockK (stubs interfaces). Do not stub what you are not testing: if the test verifies the Repository, do not stub the OkHttp client inside it — use a real MockWebServer at the HTTP level. This rule keeps tests relevant and reduces brittleness during refactoring.
Swift protocols as stubs — in the iOS-native approach, stub is implemented by substituting a test structure that conforms to the dependency protocol. Instead of a real NetworkService, the test receives a StubNetworkService that returns fixed data. Swift is a statically typed language, so the stub must conform to the same protocol as the real service. The compiler guarantees that the stub implements all required methods.
protocol NetworkServiceProtocol {
func fetchUsers() async throws -> [User]
}
struct StubNetworkService: NetworkServiceProtocol {
let result: Result<[User], Error>
func fetchUsers() async throws -> [User] {
try result.get()
}
}
final class UsersViewModelTests: XCTestCase {
func test_success_state() async {
let stub = StubNetworkService(
result: .success([User(name: "Alice")])
)
let vm = UsersViewModel(service: stub)
await vm.load()
XCTAssertEqual(vm.users.count, 1)
}
}
OCMock for Objective-C — a library for creating stubs and mocks in legacy iOS projects. OCMock supports stub methods with arguments and return values. Modern Swift projects prefer a protocol-based approach with manual stubs — this gives control over each method and does not require external dependencies. OCMock remains an option for projects where protocolizing all dependencies is economically unfeasible.
URLProtocol for HTTP stubs — a system mechanism in iOS for intercepting network requests through a URLProtocol subclass. The test registers a custom URLProtocol that intercepts URLSession and returns stub responses. The advantage over manual stubs: you do not need to change the application architecture — URLSession remains real, but data is substituted at the protocol level. The downside: harder to debug than an explicit stub service.
Frequently Asked Questions
Stub returns predefined data and does not verify whether a call occurred. Mock additionally verifies that the method was called with the correct arguments (verify). Stub answers the question “what to return”, Mock answers “was the call made”. Use stub for state verification, mock for interaction verification.
Fake is needed when the test requires a working (even if simplified) implementation — for example, an in-memory database instead of Room. Stub is suitable for single scenarios with predefined data. If you are repeating the same stub in 10 tests — you most likely need a Fake. Fake reduces duplication because the logic lives in a single class.
On Android — MockK for Kotlin objects (object) supports mockkObject(), including Java class static methods via mockkStatic(). On iOS — Swift static methods cannot be stubbed directly; use protocols and DI to replace a static call with an instance method of a protocol. Static stubs are technical debt and should be avoided in new code.
Use MockWebServer (OkHttp) — it works as a local HTTP server that enqueues responses. For Retrofit, simply replace the base URL with localhost:8080. For Ktor, use MockEngine — a built-in mechanism for substituting HttpStatement. Both approaches work without real internet and give full control over status code, body, and response headers.
Spy wraps a real object and records calls, while Stub completely replaces the object with fixed responses. Spy allows partial use of the real implementation (other methods work as is), whereas stub does not. If you need to verify that a method was called but part of the logic should still execute — use spy, not stub.
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