masteramyx
04/10/2023, 10:54 PMViewModel
// Make request every 30sec
fun startRepeatingCall(){
viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>){
while(true){
fetchFromNetwork()
delay(30000)
}
}
}
private suspend fun fetchFromNetwork(){
runCatching { repository.fetchFromNetwork() }
////
}
class TestVmClass {
@MockK
private lateinit var repository
private lateinit var viewModel
private val scheduler = TestCoroutineScheduler()
private val testDispatcher = StandardTestDispatcher(scheduler)
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
}
@Test
fun `test repeating task`() = runTest(testDispatcher) {
viewModel.startRepeatingCall()
// tried different ways of advancing virtual clock
coVerify(exactly = 3) { repository.fetchFromNetwork() }
}
viewModelScope
would use this dispatcher and when I call advanceTimeBy(x)
the "time" within that viewModelScope.launch{}
block would respect that. Thus, the delay
would reach it's end point and my fetchFromNetwork()
would be called again.Patrick Steiger
04/11/2023, 12:33 AMviewModelScope
and instead you can (e.g) pass in a CoroutineScope
that is a child of the scope created by runTest
to your ViewModel constructor
ViewModel
base class takes in a vararg of Closeable
, so you can pass CoroutineScope as a Closeable to it (so it auto closes the scope on onCleared()
(viewModelScope + dispatcher)
to run your ViewModel coroutines.
I prefer the previous suggestion because it also sets the Job so structured concurrency with runTest
job is respectedmasteramyx
04/11/2023, 1:45 PMAndroidViewModel
so i don't believe we have that constructor available.
What I don't like about 2nd approach, although it works. Is that I have to expose the new val scope = (viewModelScope + dispatcher)
because in my tests, I'm unable to cancel the coroutines running within that scope without access to it.
For example
@Test
fun `test polling every 30 seconds`() = runTest {
coEvery { repository.fetchFromNetwork() } coAnswers {
Response()
}
println("Virtual Clock Before: ${this.currentTime}")
viewModel.startRepeatingTask()
advanceTimeBy(90000)
println("Virtual Clock After: ${this.currentTime}")
viewModel.scope.cancel()
// I can't cancel coroutine running inside VM with `TestScope` or `TestDispatcher` but only with scope those coroutines were launched in
coVerify(exactly = 3) { repository.fetchFromNetwork() }
}
It makes sense that you could only cancel a coroutine from scope it was launched in. I guess I could also have one of these VM functions return a job which then I could have a reference to and cancel?Matt Rea
04/11/2023, 5:43 PM