androidengineers.Book a session

Testing Architecture

Mocks/Stubs/Fakes & Hermetic Tests

article25 minMedium

Test doubles (mocks, stubs, fakes) replace real dependencies in tests so you can test one unit of code in isolation. Choosing the right type and knowing when to avoid them altogether is critical to a healthy test suite.

The Vocabulary

TypeDescriptionWhen to use
FakeA working implementation with simplified behavior (e.g., in-memory DB)Most of the time — preferred
StubReturns hardcoded values for specific callsSimple, one-off return values
MockVerifies that specific methods were called with specific argumentsVerifying side effects
SpyWraps a real object, overriding only specific methodsRare — avoid usually

Fakes: The Best Tool

A fake is a lightweight, fully functional replacement for a real dependency:

// Real interface
interface ArticleRepository {
    suspend fun getArticle(id: String): Article?
    suspend fun saveArticle(article: Article)
    suspend fun deleteArticle(id: String)
}

// Fake: in-memory implementation — no Mockito, no mocking framework
class FakeArticleRepository : ArticleRepository {
    private val articles = mutableMapOf<String, Article>()

    override suspend fun getArticle(id: String) = articles[id]
    override suspend fun saveArticle(article: Article) { articles[article.id] = article }
    override suspend fun deleteArticle(id: String) { articles.remove(id) }

    // Test helpers — not part of the interface
    fun seedArticles(vararg articles: Article) { articles.forEach { this.articles[it.id] = it } }
    fun allArticles() = articles.values.toList()
}

// Test using the fake
class ArticleViewModelTest {
    private val repository = FakeArticleRepository()
    private val viewModel = ArticleViewModel(repository)

    @Test
    fun `loading article shows correct state`() = runTest {
        repository.seedArticles(Article("a1", "Hello World"))
        viewModel.loadArticle("a1")
        assertEquals("Hello World", viewModel.state.value.title)
    }
}

Stubs with Mockito

// implementation("org.mockito.kotlin:mockito-kotlin:5.2.1")

@Test
fun `fetch returns articles from api`() = runTest {
    val mockApi = mock<ArticleApi>()

    // Stub: define what the mock returns for a specific call
    whenever(mockApi.fetchArticles(page = 1)).thenReturn(listOf(testArticle))

    val repository = ArticleRepository(api = mockApi, dao = FakeArticleDao())
    val result = repository.getPage(1)

    assertEquals(listOf(testArticle), result)
}

Mocks for Verifying Interactions

@Test
fun `analytics event sent on article view`() = runTest {
    val mockAnalytics = mock<Analytics>()
    val viewModel = ArticleViewModel(repository, analytics = mockAnalytics)

    viewModel.onArticleOpened("a1")

    // Verify the mock was called with specific arguments
    verify(mockAnalytics).track("article_view", mapOf("id" to "a1"))
    verifyNoMoreInteractions(mockAnalytics)
}

Hermetic Tests: No Real I/O

Hermetic tests never reach the real network, database, or file system. They're fast and deterministic:

// ❌ Not hermetic — depends on real network
@Test
fun `api returns articles`() = runTest {
    val result = RealArticleApi().fetchArticles()  // real HTTP call
    assertTrue(result.isNotEmpty())
}

// ✅ Hermetic — uses MockWebServer or fake
@Test
fun `api parses article response correctly`() = runTest {
    val server = MockWebServer().also {
        it.enqueue(MockResponse().setBody("""[{"id":"a1","title":"Hello"}]"""))
        it.start()
    }

    val api = Retrofit.Builder().baseUrl(server.url("/")).build().create(ArticleApi::class.java)
    val result = api.fetchArticles()

    assertEquals("Hello", result[0].title)
    server.shutdown()
}

When to Avoid Mocks

  • Don't mock the database: use Room's in-memory database instead. Mocking Room DAOs tests nothing useful and misses real SQL bugs.
  • Don't mock your own code: if you're mocking a class you own, consider extracting an interface and using a fake.
  • Don't mock data classes: just instantiate them with test values.

Key Takeaways

RuleWhy
Prefer fakes over mocksFakes compile-check the interface; mocks can silently miss refactors
Use mocks for verifying side effectsChecking analytics events, notification sends — not return values
In-memory Room for database testsTests real SQL; Room.inMemoryDatabaseBuilder is fast
MockWebServer for HTTPTests real JSON parsing; no network dependency
Hermetic = no real I/OFast, deterministic, no flakiness from network/disk state

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Mocks/Stubs/Fakes & Hermetic Tests | Android System Design | Android Engineers