A tiny library for easier testing of Kotlin Flows in a sequential way, without direct usage
of backgroundScope.launch { ... }.
// 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.