What is the best strategy to inject viewModelScope
for Android unit tests with Kotlin coroutines?
When the Coroutin
In production, the ViewModel is created with a null coroutineScopeProvider
, as the ViewModel's viewModelScope
is used. For testing, TestCoroutineScope
is passed as the ViewModel argument.
SomeUtils.kt
/**
* Configure CoroutineScope injection for production and testing.
*
* @receiver ViewModel provides viewModelScope for production
* @param coroutineScope null for production, injects TestCoroutineScope for unit tests
* @return CoroutineScope to launch coroutines on
*/
fun ViewModel.getViewModelScope(coroutineScope: CoroutineScope?) =
if (coroutineScope == null) this.viewModelScope
else coroutineScope
SomeViewModel.kt
class FeedViewModel(
private val coroutineScopeProvider: CoroutineScope? = null,
private val repository: FeedRepository
) : ViewModel() {
private val coroutineScope = getViewModelScope(coroutineScopeProvider)
fun getSomeData() {
repository.getSomeDataRequest().onEach {
// Some code here.
}.launchIn(coroutineScope)
}
}
SomeTest.kt
@ExperimentalCoroutinesApi
class FeedTest : BeforeAllCallback, AfterAllCallback {
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
private val repository = mockkClass(FeedRepository::class)
private var loadNetworkIntent = MutableStateFlow<LoadNetworkIntent?>(null)
override fun beforeAll(context: ExtensionContext?) {
// Set Coroutine Dispatcher.
Dispatchers.setMain(testDispatcher)
}
override fun afterAll(context: ExtensionContext?) {
Dispatchers.resetMain()
// Reset Coroutine Dispatcher and Scope.
testDispatcher.cleanupTestCoroutines()
testScope.cleanupTestCoroutines()
}
@Test
fun topCafesPoc() = testDispatcher.runBlockingTest {
...
val viewModel = FeedViewModel(testScope, repository)
viewmodel.getSomeData()
...
}
}