MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Mocks, fakes, stubs, and spies are test doubles — but they're not interchangeable. Using the wrong one leads to over-specified tests that break on refactoring or under-specified tests that miss bugs. Here's when to use each.
On this page
The terms "mock," "fake," and "stub" are often used interchangeably in the wild. They're not the same thing, and the distinction matters for test design.
Using the wrong test double leads to brittle tests, tests that miss bugs, or tests that test the framework instead of your code.
Stub — returns hard-coded responses. No behavior. No verification.
Fake — a simplified working implementation. Has real behavior, simplified for testing.
Mock — a fake that also records interactions for later verification. The test asserts that specific methods were called.
Spy — a wrapper around a real object that records calls. Same behavior as real, but observable.
A stub answers questions with pre-configured responses:
// Stub using MockK
val userRepository = mockk<UserRepository>()
every { userRepository.findById("user-1") } returns User("user-1", "Alice", "alice@example.com")
// The stub doesn't care how many times findById is called or with what elseUse stubs when:
A fake is a real implementation that's simpler than the production version:
class FakeUserRepository : UserRepository {
private val users = mutableMapOf<String, User>()
override suspend fun findById(id: String): User? = users[id]
override suspend fun save(user: User) {
users[user.id] = user
}
override suspend fun delete(id: String) {
users.remove(id)
}
// Test helper — not in the interface
fun add(user: User) = users.put(user.id, user)
fun count() = users.size
}Fakes are valuable because:
In-memory databases are the classic fake: Room's in-memory database builder creates a fake that behaves exactly like the real database — just without persistence.
Use fakes when:
Mocks track how they were used. You assert on those interactions:
val emailService = mockk<EmailService>()
every { emailService.sendWelcomeEmail(any()) } just Runs
val authService = AuthService(emailService)
authService.registerUser("alice@example.com")
// Verify the mock was called correctly
verify { emailService.sendWelcomeEmail("alice@example.com") }
verify(exactly = 1) { emailService.sendWelcomeEmail(any()) }Use mocks when:
[!WARNING] Mocks that verify every method call are over-specified. If you refactor the implementation but keep the same behavior, the test breaks because the interaction pattern changed. Verify the calls that are semantically important, not all calls.
A spy wraps a real object and records calls without changing behavior:
val realRepository = TaskRepositoryImpl(realDatabase)
val spyRepository = spyk(realRepository)
taskService.processTask("task-1", repository = spyRepository)
// Verify the real method was called
verify { spyRepository.getTask("task-1") }
verify { spyRepository.update(any()) }Spies are useful when:
Spies are also the most dangerous test double — they use real implementations, so test isolation is weaker.
Does the test need to verify a specific call was made?
├── Yes → Mock
└── No
Does the test need stateful behavior across multiple calls?
├── Yes → Fake
└── No
Does the test just need a specific return value?
└── StubMocking everything. When every dependency in a test is a mock, you end up testing the interaction protocol, not the behavior. A mock-heavy test breaks when you refactor — even if behavior is unchanged.
// Over-mocked — tests interactions, not behavior
val dao = mockk<TaskDao>()
val cache = mockk<TaskCache>()
val logger = mockk<TaskLogger>()
every { dao.getTask("1") } returns taskEntity
every { cache.get("1") } returns null
every { logger.log(any()) } just Runs
taskRepository.getTask("1")
verify { dao.getTask("1") }
verify { cache.get("1") }
verify { logger.log(any()) } // Who cares if it logs?Using fakes for interaction verification. Fakes don't record calls. If you need to verify a call happened, use a mock.
Using mocks instead of fakes for state. Configuring mock return values for every possible call sequence becomes unmanageable. Use a fake with real state.
class TaskServiceTest {
// Fake — stateful, reusable, supports multiple operations
private val repository = FakeTaskRepository()
// Stub — only needs to return a value
private val clock = mockk<Clock>()
// Mock — need to verify the email was sent
private val notifier = mockk<NotificationService>()
private val service = TaskService(repository, clock, notifier)
@Before
fun setup() {
every { clock.now() } returns fixedTime
every { notifier.sendReminder(any()) } just Runs
}
@Test
fun `overdue tasks trigger reminder notifications`() = runTest {
repository.add(Task("1", "Overdue task", dueDate = yesterday))
service.checkOverdueTasks()
verify { notifier.sendReminder("1") } // Mock verifies the side effect
assertEquals(1, repository.count()) // Fake verifies state wasn't changed
}
}Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read