Pruebas unitarias de ViewModel con Kotlin Coroutines y LiveData en Android

La documentación oficial dice que las rutinas son subprocesos ligeros. Por ligero, significa que la creación de rutinas no asigna nuevos subprocesos. En su lugar, utilizan grupos de subprocesos predefinidos y programación inteligente con el propósito de qué tarea ejecutar a continuación y qué tareas más adelante. En este artículo, aprenderemos a escribir una prueba de unidad para un modelo de vista usando Kotlin Coroutines y LiveData y adhiriéndose a una arquitectura MVVM básica. Estamos escribiendo una prueba unitaria para verificar los datos anteriores. Para simplificar las cosas, el proyecto emplea una arquitectura MVVM básica. El código completo para las pruebas unitarias mencionado en el blog se puede encontrar en el propio proyecto.

Implementación paso a paso

Este SingleNetworkCallViewModel es esencialmente un ViewModel que está asociado con una SingleNetworkCallActivity, lo que hace que ViewModel obtenga una lista de usuarios para representarla en la interfaz de usuario. SingleNetworkCallViewModel luego usa ApiHelper para consultar la capa de datos para obtener una lista de usuarios. ViewModel, como se muestra a continuación, utiliza Kotlin Coroutines y LiveData.

Kotlin

class SingleNetworkCallViewModel(
    private val gfgApi: GfgApi,
    private val gfgDB: DatabaseHelper
) : ViewModel() {
    private val gfgUsers = MutableLiveData<Resource<List<ApiUser>>>()
    init {
        fetchGfgUsers()
    }
    private fun fetchGfgUsers() {
        gfgVM.launch {
            gfgUsers.postValue(Resource.loading(null))
            try {
                val gfgUsersFromApi = gfgApi.getGfgUsers()
                gfgUsers.postValue(Resource.success(gfgUsersFromApi))
            } catch (e: Exception) {
                gfgUsers.postValue(Resource.error(e.toString(), null))
            }
        }
    }
    fun getGfgUsers(): LiveData<Resource<List<ApiUser>>> {
        return gfgUsers
    }
}

Ahora debemos escribir una prueba unitaria para este ViewModel, que utiliza Kotlin Coroutines y LiveData . Primero, debemos configurar las dependencias de la prueba, como se muestra a continuación:

testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:3.3.2"
testImplementation 'androidx.arch.core:core-testing:2.1.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2'

Asegúrese de estar utilizando la versión más reciente disponible en el momento de leer este artículo. Esto es importante porque cada versión incluye numerosas correcciones de errores. Pasemos al paquete de prueba, donde escribiremos la prueba unitaria de ViewModel. Ahora debemos crear la TestRule, a la que llamaremos TestCoroutineRule, y colocarla en el paquete utils.

Kotlin

@ExperimentalCoroutinesApi
class GfgCourtine : TestRule {
    private val gfgCourtineDispatcher = GfgCourtineDispatcher()
    private val gfgCourtineDispatcherScope = GfgCourtineDispatcherScope(gfgCourtineDispatcher)
    override fun apply(base: Statement, description: Description?) = object : Statement() {
        @Throws(Throwable::class)
        override fun evaluate() {
            Dispatchers.setMain(gfgCourtineDispatcher)
            base.evaluate()
            Dispatchers.resetMain()
            gfgCourtineDispatcherScope.cleanupTestCoroutines()
        }
    }
    fun runBlockingTest(block: suspend GfgCourtineDispatcherScope.() -> Unit) =
        gfgCourtineDispatcherScope.runBlockingTest { block() }
}

¿Por qué se usa la TestRule anterior?

Permite que el despachador principal use TestCoroutineDispatcher durante la prueba unitaria. Se restablece y se limpia después de la prueba. Ahora, agregaremos SingleNetworkCallViewModelTest a la ubicación adecuada dentro del paquete de prueba, como se muestra a continuación:

Kotlin

@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class GfgSingle {
    
    @get:Rule
    val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
    @get:Rule
    val gfgTest = TestCoroutineRule()
    @Mock
    private lateinit var gfgApi: GfgApi
    @Mock
    private lateinit var gfgDBHelper: GfgDBHelper
    @Mock
    private lateinit var apiUsersObserver: Observer<Resource<List<ApiUser>>>
    @Before
    fun doSomeSetup() {
        // do something if required
    }
      
    @Test
    fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
        testCoroutineRule.runBlockingTest {
            doReturn(emptyList<ApiUser>())
                .`when`(gfgApi)
                .getGfgUser()
            val viewModel = SingleNetworkCallViewModel(gfgApi, gfgDBHelper)
            viewModel.getGfgUser().observeForever(apiUsersObserver)
            verify(gfgApi).getGfgUser()
            verify(apiUsersObserver).onChanged(Resource.success(emptyList()))
            viewModel.getGfgUser().removeObserver(apiUsersObserver)
        }
    }
      
    @Test
    fun givenServerResponseError_whenFetch_shouldReturnError() {
        testCoroutineRule.runBlockingTest {
            val someGeekyError = "Something is not right"
            doThrow(RuntimeException(someGeekyError))
                .`when`(gfgApi)
                .getGfgUser()
            val viewModel = SingleNetworkCallViewModel(gfgApi, gfgDBHelper)
            viewModel.getGfgUser().observeForever(apiUsersObserver)
            verify(gfgApi).getGfgUser()
            verify(apiUsersObserver).onChanged(
                Resource.error(
                    RuntimeException(someGeekyError).toString(),
                    null
                )
            )
            viewModel.getGfgUser().removeObserver(apiUsersObserver)
        }
    }
      
    @After
    fun tearDown() {
        // do something if required
    }
      
}

En este caso, usamos InstantTaskExecutorRule, que se requiere cuando se prueba código con LiveData. Si no usamos esto, obtendremos una RuntimeException en Android relacionada con Looper. Nos burlamos de ApiHelper , DatabaseHelper y otros componentes y escribimos dos pruebas:

  1. Cuando el servidor devuelve 200, la capa de la interfaz de usuario debería tener éxito.
  2. Cuando el servidor devuelve un error, la capa de la interfaz de usuario también debería recibir un error.

En el primero, nos burlamos de ApiHelper, lo que provocó que devolviera el éxito con una lista vacía. Luego buscamos y validamos. De manera similar, en el segundo, nos burlamos de ApiHelper para devolver un error. Luego buscamos y validamos. ¡Nos hemos burlado con éxito de los datos y ahora estamos listos para usar todos estos métodos!

Publicación traducida automáticamente

Artículo escrito por icloudanshu y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *