Architecture & Standards

Testing — Android / KMP

Goal: fewer bugs at low cost. Test the logic that breaks, skip the trivial.

1. What to test (priority order)

Must test (high value, cheap)Skip (low value)
Reducers — pure (state, intent) → state, every branchComposable pixels (use previews instead)
Use cases — business rules, success + failure pathsGetters / data class equality
Mappers / validators — every edge caseFramework/DI wiring
Conflict resolution & offline queue logicThird-party libs
Repository impls — local read/write + enqueue (with fakes)

Reducers are pure → testing them is trivial and catches most state bugs. Aim 100% branch coverage on reducers, ~90% on use cases.

2. Patterns

Reducer (no coroutines needed — it's pure)

@Test
fun `QueryChanged filters visible items`() {
    val vm = CartViewModel(getTotal = FakeGetTotal(), dispatchers = TestDispatchers())
    vm.onIntent(CartIntent.Loaded(listOf(apple, banana)))

    vm.onIntent(CartIntent.QueryChanged("app"))

    assertThat(vm.state.value.visibleItems).containsExactly(apple)
}

Use case (runTest + fake repo)

@Test
fun `returns Failure when repo errors`() = runTest {
    val useCase = GetCartTotalUseCase(FakeCartRepository(error = AppError.Storage))
    val result = useCase(cartId)
    assertThat(result).isInstanceOf(AppResult.Failure::class.java)
}

Flow (Turbine)

repo.observeItems().test {
    assertThat(awaitItem()).isEqualTo(AppResult.Success(emptyList()))
    cancelAndIgnoreRemainingEvents()
}

3. Rules

4. Coverage targets (suggested, CI-enforced)

AreaLineBranch
Reducers95%100%
Use cases90%85%
Mappers / validators100%100%
Crypto / security-critical100%100%
Everything else70%

Stack: kotlin-test / JUnit, Turbine (Flow), Truth or kotlin-test assertions.