Recipe: Test a presenter that navigates¶
Problem: verify that a presenter emits the right states and navigates where you expect, in response to events.
Use Presenter.test {} from circuit-test and a FakeNavigator. test {} gives you a Turbine for
presenter states. Drive the presenter by invoking the eventSink on an emitted state.
@Test
fun `clicking an item navigates to its detail`() = runTest {
val navigator = FakeNavigator(FeedScreen)
val presenter = FeedPresenter(FeedScreen, navigator, FakeFeedRepository(items = listOf(item1)))
presenter.test {
// Consume the initial loading emission before asserting on loaded state.
assertEquals(FeedState.Loading, awaitItem())
val loaded = assertIs<FeedState.Loaded>(awaitItem())
loaded.eventSink(FeedEvent.OpenItem(item1.id)) // simulate the tap
assertEquals(DetailScreen(item1.id), navigator.awaitNextScreen())
}
}
Common FakeNavigator assertions:
awaitNextScreen()— the next screen pushed viagoToawaitPop()— apop()happenedawaitResetRoot()— aresetRoot()happenedassertGoToIsEmpty()— assert no navigation occurred
Things to watch for:
- Consume
Loadingfirst. A presenter that starts inLoadingemits it before the loaded state.assertIs<FeedState.Loaded>(awaitItem())on the first item will fail — await the loading emission first. awaitItem()is distinct-until-changed. Identical consecutive states collapse into one, so you assert state changes, not every recomposition. When you specifically want to assert a recomposition produced no change, use the escape hatchawaitUnchanged()— it awaits the next emission and fails if it differs from the previous one.
For UI-level event assertions, render the composable with a TestEventSink and assert the events it
emits — see the testing doc.
See also: Testing · Test a presenter that shows an overlay