A tiny library for easier testing of Kotlin Flows in a sequential way, without direct usage
of backgroundScope.launch { ... }.
Maven Central License: Apache 2
// flowtest library: testImplementation "com.uandcode.flowtest:1.1.0" // + default jetbrains library for testing coroutines if needed: testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2"
Let's test the Flow.debounce() operator.
@Test fun testDebouncedFlow() = runFlowTest { // <-- write runFlowTest instead of runTest val inputFlow = MutableSharedFlow<String>() val collector = inputFlow .debounce(100) // let's test how debounce works .startCollecting() // create test collector assertFalse(collector.hasItems) // assert no items at the beginning inputFlow.emit("item") advanceTimeBy(99) // wait 99 millis -> still no output assertFalse(collector.hasItems) advanceTimeBy(2) // wait a bit more, 101ms in total -> now the item should be emitted assertEquals("item", collector.lastItem) assertEquals(1, collector.count) }
Real-world scenario: here is a test verifying the repository.getCurrentUser() call
returns a Flow with the current authorized user, even after executing other operations
such as signIn() or logout().
@Test fun testFlow() = runFlowTest { // <-- write runFlowTest instead of runTest val repository: UserRepository = createRepository() val currentUserFlow: Flow<User> = repository.getCurrentUser() val testFlowCollector: TestFlowCollector<User> = currentUserFlow.startCollecting() // assert the latest collected item assertEquals(User.ANONYMOUS, testFlowCollector.lastItem) // do something to make the flow to produce a new value repository.signIn("email", "password") // assert the latest collected item again assertEquals(User("email", "password"), testFlowCollector.lastItem) // example of other assertions: assertEquals(2, testFlowCollector.count) assertTrue(testFlowCollector.hasItems) assertEquals( listOf(User.ANONYMOUS, User("email", "password")), testFlowCollector.collectedItems, ) assertEquals(CollectStatus.Collecting, testFlowCollector.collectStatus) }
- If your code has
delay()calls or other similar calls, useadvanceTimeBy()in unit tests for managing virtual time. - If your code uses
CoroutineScopeinstances, do not forget to replace them in unit tests byTestScopeor evenbackgroundScope(for infinite jobs/flows). For example, inject the test/background scope into the constructor when creating the object. - Make sure you are using either
UnconfinedTestDispatcherorStandardTestDispatcherinstead of real dispatchers likeDispatchers.IOand so on. It is preferred to inject test dispatcher via class constructor. - For testing android ViewModels, replace
Dispatchers.MainbyUnconfinedTestDispatcherorStandardTestDispatcher. You can set main dispatcher viaDispatchers.setMain(testDispatcher)and then reset it viaDispatchers.resetMain().
Check out this link for more details.