J'écris des tests unitaires pour mon viewModel, mais j'ai du mal à exécuter les tests. Le bloc runBlocking { ... }
N'attend pas réellement la fin du code à l'intérieur, ce qui m'étonne.
Le test échoue car result
est null
. Pourquoi runBlocking { ... }
N'exécute-t-il pas le bloc launch
à l'intérieur du ViewModel de manière bloquante?
Je sais que si je le convertis en une méthode async
qui renvoie un objet Deferred
, alors je peux obtenir l'objet en appelant await()
, ou je peux retourner un Job
et appelez join()
. Mais, je voudrais le faire en laissant mes méthodes ViewModel en tant que fonctions void
, y a-t-il un moyen de le faire?
// MyViewModel.kt
class MyViewModel(application: Application) : AndroidViewModel(application) {
val logic = Logic()
val myLiveData = MutableLiveData<Result>()
fun doSomething() {
viewModelScope.launch(MyDispatchers.Background) {
System.out.println("Calling work")
val result = logic.doWork()
System.out.println("Got result")
myLiveData.postValue(result)
System.out.println("Posted result")
}
}
private class Logic {
suspend fun doWork(): Result? {
return suspendCoroutine { cont ->
Network.getResultAsync(object : Callback<Result> {
override fun onSuccess(result: Result) {
cont.resume(result)
}
override fun onError(error: Throwable) {
cont.resumeWithException(error)
}
})
}
}
}
// MyViewModelTest.kt
@RunWith(RobolectricTestRunner::class)
class MyViewModelTest {
lateinit var viewModel: MyViewModel
@get:Rule
val rule: TestRule = InstantTaskExecutorRule()
@Before
fun init() {
viewModel = MyViewModel(ApplicationProvider.getApplicationContext())
}
@Test
fun testSomething() {
runBlocking {
System.out.println("Called doSomething")
viewModel.doSomething()
}
System.out.println("Getting result value")
val result = viewModel.myLiveData.value
System.out.println("Result value : $result")
assertNotNull(result) // Fails here
}
}
J'ai essayé la meilleure réponse et j'ai travaillé, mais je ne voulais pas passer en revue tous mes lancements et ajouter une référence de répartiteur à principal ou non confiné dans mes tests. J'ai donc fini par ajouter ce code à ma classe de test de base. Je définis le répartiteur principal comme non confiné
private val testingDispatcher = Dispatchers.Unconfined
@BeforeEach
private fun doBeforeEach() {
Dispatchers.setMain(testingDispatcher)
}
@AfterEach
private fun doAfterEach() {
Dispatchers.resetMain()
}
Dans mon cas, j'utilise un Flow
qui est ensuite commuté sur LiveData
, j'ai pu implémenter observeForever
dans le test unitaire, et le point d'arrêt s'arrête à l'intérieur de celui-ci.