web-dev-qa-db-fra.com

Coroutines - tests unitaires méthodes viewModelScope.launch

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
    }
}

11
Prem

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.

0
Juan Mendez