es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿Cómo probar los corutinas View Model que bloquean el hilo antes de las aserciones?

Estoy tratando de escribir una prueba para un Modelo de Vista. Estoy haciendo una prueba con instrumentación ya que necesito el “contexto”.

El modelo de vista y la prueba se ven así:

class MyViewModel(
    private val dispatcher: CoroutineDispatchers = Dispatchers.IO) : ViewModel() {
    private val _livedata = MutableLiveData<boolean>()
    val livedata: LiveData<boolean> = _livedata

    fun doSomething() {
        viewModelScope.launch(dispatcher) {
            // función suspendida con retrofit
            _livedata.value = true
        }
    }
}

class MyViewModelTest {
    private lateinit var viewModel: MyViewModel

    @get:Rule
    var mainCoroutineRule = MainCoroutineRule()

    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setup() {
        viewModel = MyViewModel(mainCoroutineRule.dispatcher)
    }

    @Test
    fun testMyViewModel() {
        mainCoroutineRule.runBlockingTest {
            viewModel.doSomething()

            mainCoroutineRule.dispatcher.advanceUntilIdle()

            val result = viewModel.livedata.getOrAwaitValue()

            assertThat(result).isTrue()
        }
    }
}

El problema está en cómo “result” es nulo debido a que “doSomething()” es llamado en otro hilo y se ejecuta de forma asincrónica.

¿Cómo puedo ejecutar mi prueba para que la función suspendida bloquee el hilo y mi afirmación capture el “result” después de que la función se haya terminado?

Estoy bastante confundido con la información que hay allá afuera.

Creo que no necesito “InstantTaskExecutorRule()” porque estoy haciendo una prueba con instrumentación.

Agregar esta regla no ayuda:

@ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
   TestWatcher(),
   TestCoroutineScope by TestCoroutineScope(dispatcher) {
   override fun starting(description: Description?) {
       super.starting(description)
       Dispatchers.setMain(dispatcher)
   }

   override fun finished(description: Description?) {
       super.finished(description)
       cleanupTestCoroutines()
       Dispatchers.resetMain()
   }
}

¿Necesito inyectar un despachador de hilos de ejecución en mi modelo de vista que bloquee el hilo principal?

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Para asegurarse de que la función de suspensión en su modelo de vista bloquee el hilo para que su afirmación capture el resultado después de que la función de suspensión haya terminado, puede reemplazar el despachador IO con un TestCoroutineDispatcher en su modelo de vista.

    Para hacer esto, necesita modificar el constructor de su MyViewModel para tomar un parámetro de CoroutineDispatcher con un valor predeterminado de Dispatchers.IO. Luego, en su configuración de prueba, puede pasar un TestCoroutineDispatcher en lugar de Dispatchers.IO.

    Aquí hay una versión actualizada de su código:

    class MyViewModel(
        private val dispatcher: CoroutineDispatcher = Dispatchers.IO
    ) : ViewModel() {
        private val _livedata = MutableLiveData<boolean>()
        val livedata: LiveData<boolean> = _livedata
    
        fun doSomething() {
            viewModelScope.launch(dispatcher) {
                // suspend function with retrofit
                _livedata.value = true
            }
        }
    }
    
    class MyViewModelTest {
        private lateinit var viewModel: MyViewModel
    
        private val testDispatcher = TestCoroutineDispatcher()
    
        @Before
        fun setup() {
            Dispatchers.setMain(testDispatcher) // Required for LiveData to work in tests
            viewModel = MyViewModel(testDispatcher)
        }
    
        @After
        fun cleanup() {
            Dispatchers.resetMain()
            testDispatcher.cleanupTestCoroutines()
        }
    
        @Test
        fun testMyViewModel() = testDispatcher.runBlockingTest {
            viewModel.doSomething()
    
            // Advance time to allow the coroutine to finish
            testDispatcher.advanceUntilIdle()
    
            val result = viewModel.livedata.getOrAwaitValue()
    
            assertThat(result).isTrue()
        }
    }
    

    Tenga en cuenta que establecer el despachador principal en testDispatcher es necesario para que LiveData funcione correctamente en las pruebas.

    Además, en la función testMyViewModel, estamos usando testDispatcher.runBlockingTest en lugar de mainCoroutineRule.runBlockingTest. Esto se debe a que ya no estamos usando reglas de corutinas principales, por lo que debemos usar directamente el TestCoroutineDispatcher.

    Finalmente, en lugar de llamar a mainCoroutineRule.dispatcher.advanceUntilIdle(), estamos llamando a testDispatcher.advanceUntilIdle() para avanzar en el tiempo en el despachador de prueba.

    ¡Espero que esto ayude!

Comments are closed.