Luca Nicoletti
08/07/2023, 9:47 AMviewModelScope.launch {}
a while(isActive)
loop where I do things.
I’m trying to test that viewModel, mocking everything it needs.
The test is run inside a runTest
block.
The test reaches the assertEqual
line, which passes, but then it doesn’t cancel the launch
and that keeps running (found it out while debugging).
Am I doing something wrong?Stepan Churyukanov
08/07/2023, 10:00 AMclass ReplaceMainDispatcherExtension(
private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
) : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
}
}
Luca Nicoletti
08/07/2023, 10:00 AMLuca Nicoletti
08/07/2023, 10:00 AMLuca Nicoletti
08/07/2023, 10:00 AMStepan Churyukanov
08/07/2023, 10:03 AMLuca Nicoletti
08/07/2023, 10:05 AMzsmb
08/07/2023, 12:51 PMLuca Nicoletti
08/07/2023, 12:52 PMStepan Churyukanov
08/07/2023, 12:53 PMclass MyViewModel: ViewModel() {
fun runSomething(scope: CoroutinesScope = viewModelScope) {
scope.launch {
// your problematic code
}
}
}
class MyViewModelTest {
val scope = TestScope(UnconfinedTestDispatcher())
@Test
fun test() {
MyViewModel().runSomething(scope)
scope.cancel()
}
}
it workszsmb
08/07/2023, 12:54 PMTestScope.backgroundScope
is something you can pass into the object you're testing, as coroutines in there will be cancelled at the end of runTest
.Luca Nicoletti
08/07/2023, 12:55 PMviewModelScope.launch
- without parameter - to be cancelled as well?Luca Nicoletti
08/07/2023, 12:55 PM@Rule
I can setup on my testszsmb
08/07/2023, 12:57 PMviewModelScope
directly (which generally makes testing more difficult, unfortunately), you can attempt to clear the ViewModel at the end of the test, which would also cancel that scope. Not sure off the top of my head how difficult that is to achieve though.zsmb
08/07/2023, 12:58 PMLuca Nicoletti
08/07/2023, 1:07 PMviewModelScope
but provided dispatchers in the launch
, using different ones for testing. Would that work as well?zsmb
08/07/2023, 4:16 PMJob
into the ViewModel that you then pass in as an extra parameter every time you call viewModelScope.launch()
, then each of those newly created coroutines would become children of the injected Job
, and cancelling the parent would cancel them as well.
This would, however, mean that the coroutines are no longer tied to the ViewModel's lifecycle, so you'd need to also cancel the injected job whenever the ViewModel is cleared, for example like this https://developer.android.com/topic/libraries/architecture/viewmodel#clear-dependencies
Having to remember to pass it in every time you launch a coroutine though, my opinion is that this isn't a clean way to do it. Ideally a scope where you're launching should already contain a correct configuration for its coroutines (dispatcher, error handler, and especially parent job), with an extra context parameter only required in rare cases.Haroon Iftikhar
08/07/2023, 6:02 PMErfannj En
08/08/2023, 8:04 AMLuca Nicoletti
08/08/2023, 8:16 AMyield
Erfannj En
08/08/2023, 8:17 AMLuca Nicoletti
08/08/2023, 9:42 AM