Imagine my ViewModel looks like this :
class ViewModelA(
repository: Repository,
ioCoroutineDispatcher: CoroutineDispatcher,
) : ViewModel() {
val liveData : LiveData<String> = repository.getFooBarFlow().asLiveData(ioCoroutineDispatcher)
}
Imagine my Repository implementation looks like this :
class RepositoryImpl : Repository() {
override fun getFooBarFlow() : Flow<String> = flow {
emit("foo")
delay(300)
emit("bar")
}
}
How could I unit test the fact that "foo" is emitted immediately by the LiveData
, then 300ms later (no more, no less), that "bar" is emitted by the LiveData
?
You can use JUnit 4 or 5, but you have to use Kotlin 1.6, kotlinx-coroutines-test 1.6 and runTest {}
instead of runBlockingTest {}
(I have no issues testing with runBlockingTest {}
)
CodePudding user response:
The new TestCoroutineRule
in coroutine-test 1.6 looks like this :
@ExperimentalCoroutinesApi
class TestCoroutineRule : TestRule {
val testCoroutineDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testCoroutineDispatcher)
override fun apply(base: Statement, description: Description?) = object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
Dispatchers.setMain(testCoroutineDispatcher)
base.evaluate()
Dispatchers.resetMain()
}
}
fun runTest(block: suspend TestScope.() -> Unit) = testScope.runTest { block() }
}
runTest
behave a lot more differently than runBlockingTest
:
runTest() will automatically skip calls to delay() and handle uncaught exceptions. Unlike runBlockingTest(), it will wait for asynchronous callbacks to handle situations where some code runs in dispatchers that are not integrated with the test module. (source)
Take note, advanceTimeBy(n)
doesn't really advance the virtual coroutine time by n. The difference with the 1.5 version is it won't execute tasks scheduled at n, only tasks scheduled at n - 1. To execute tasks schedule at n, you need to use the new function runCurrent()
.
An example implementation for the test would be :
@ExperimentalCoroutinesApi
class DetailViewModelTest {
@get:Rule
val testCoroutineRule = TestCoroutineRule()
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun getFooBarFlow() = testCoroutineRule.runTest {
// Given
val fakeRepository = object : Repository {
override fun getFooBarFlow(): Flow<String> = flow {
emit("foo")
delay(300)
emit("bar")
}
}
// When
val liveData = ViewModelA(fakeRepository, testCoroutineRule.testCoroutineDispatcher).liveData
liveData.observeForever { }
// Then
runCurrent()
assertEquals("foo", liveData.value)
advanceTimeBy(300)
runCurrent()
assertEquals("bar", liveData.value)
}
}
A complete implementation with some helpers can be found here : https://github.com/NinoDLC/HiltNavArgsDemo/commit/c2d84dd79c846b96d419217eb68dd7e12baedeb6