Cómo compilar pruebas de unidades locales

Una prueba local se ejecuta directamente en tu propia estación de trabajo, en lugar de un dispositivo o emulador de Android. Por lo tanto, usa tu máquina virtual Java (JVM) local, en lugar de un dispositivo Android, para ejecutar pruebas. Las pruebas locales te permiten evaluar la lógica de tu app con mayor rapidez. Sin embargo, no poder interactuar con el framework de Android crea una limitación en los tipos de pruebas que puedes ejecutar.

Una prueba unitaria verifica el comportamiento de una sección pequeña de código, la unidad que se está probando. Para ello, ejecuta ese código y verifica el resultado.

Por lo general, las pruebas de unidades son simples, pero su configuración puede ser problemática cuando la unidad que se está probando no se diseñó teniendo en cuenta la capacidad de prueba:

  • El código que quieres verificar debe ser accesible desde una prueba. Por ejemplo, no puedes probar un método privado directamente. En su lugar, prueba la clase con sus APIs públicas.
  • Para ejecutar pruebas de unidades de forma aislada, las dependencias de la unidad que se está probando se deben reemplazar por componentes que controles, como objetos falsos o dobles de prueba. Esto es especialmente problemático si tu código depende del framework de Android.

Para obtener información sobre las estrategias comunes de pruebas de unidades en Android, consulta Qué probar.

Ubicación de las pruebas locales

De forma predeterminada, los archivos de origen de las pruebas de unidades locales se colocan en module-name/src/test/. Este directorio ya existe cuando creas un proyecto nuevo con Android Studio.

Cómo agregar dependencias de prueba

También debes configurar las dependencias de prueba para que tu proyecto use las APIs estándar que proporciona el framework de pruebas de JUnit.

Para ello, abre el archivo build.gradle del módulo de tu app y especifica las siguientes bibliotecas como dependencias. Usa la función testImplementation para indicar que se aplican al conjunto de orígenes de prueba local y no a la aplicación:

dependencies {
  // Required -- JUnit 4 framework
  testImplementation "junit:junit:$jUnitVersion"
  // Optional -- Robolectric environment
  testImplementation "androidx.test:core:$androidXTestVersion"
  // Optional -- Mockito framework
  testImplementation "org.mockito:mockito-core:$mockitoVersion"
  // Optional -- mockito-kotlin
  testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
  // Optional -- Mockk framework
  testImplementation "io.mockk:mockk:$mockkVersion"
}

Cómo crear una clase de prueba de unidades local

Escribe tu clase de prueba de unidades local como una clase de prueba de JUnit 4.

Para ello, crea una clase que contenga uno o más métodos de prueba, por lo general, en module-name/src/test/. Un método de prueba comienza con la anotación @Test y contiene el código para ejercitar y verificar un solo aspecto del componente que deseas probar.

En el siguiente ejemplo, se muestra cómo implementar una clase de prueba de unidades local. El método de prueba emailValidator_correctEmailSimple_returnsTrue() intenta verificar isValidEmail(),que es un método dentro de la app. La función de prueba devolverá verdadero si isValidEmail() también devuelve verdadero.

Kotlin

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class EmailValidatorTest {
  @Test fun emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"))
  }

}

Java

import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

class EmailValidatorTest {
  @Test
  public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"));
  }
}

Debes crear pruebas legibles que evalúen si los componentes de tu app devuelven los resultados esperados. Te recomendamos que uses una biblioteca de aserciones, como junit.Assert, Hamcrest o Truth. El fragmento anterior es un ejemplo de cómo usar junit.Assert.

Biblioteca de Android simulable

Cuando ejecutas pruebas de unidades locales, el complemento de Android para Gradle incluye una biblioteca que contiene todas las APIs del framework de Android, que corresponden a la versión que se usa en tu proyecto. La biblioteca contiene todos los métodos y las clases públicos de esas APIs, pero se quitó el código dentro de los métodos. Si se accede a cualquiera de los métodos, la prueba arroja una excepción.

Esto permite que se compilen pruebas locales cuando se hace referencia a clases en el framework de Android, como Context. Y lo que es más importante, te permite usar un framework de simulación con clases de Android.

Simula dependencias de Android

Un problema típico es descubrir que una clase usa un recurso de cadenas. Puedes obtener recursos de cadenas llamando al método getString() en la clase Context. Sin embargo, una prueba local no puede usar Context ni ninguno de sus métodos, ya que pertenecen al framework de Android. Lo ideal sería que la llamada a getString() se moviera fuera de la clase, pero esto no siempre es práctico. La solución es crear una simulación o un stub de Context que siempre devuelva el mismo valor cuando se invoque su método getString().

Con la biblioteca de Android simulable y los frameworks de simulación, como Mockito o MockK, puedes programar el comportamiento de las simulaciones de las clases de Android en tus pruebas de unidades.

Para agregar un objeto de simulación a tu prueba de unidades local con Mockito, sigue este modelo de programación:

  1. Incluye la dependencia de la biblioteca de Mockito en tu archivo build.gradle, como se describe en Cómo configurar tu entorno de pruebas.
  2. Al principio de la definición de la clase de prueba de unidades, agrega la anotación @RunWith(MockitoJUnitRunner.class). Esta anotación le indica al ejecutor de pruebas de Mockito que valide que el uso del framework sea correcto y simplifica la inicialización de los objetos de simulación.
  3. Para crear un objeto de simulación para una dependencia de Android, agrega la anotación @Mock antes de la declaración del campo.
  4. Para simular el comportamiento de la dependencia, puedes especificar una condición y un valor de retorno cuando se cumpla la condición con los métodos when() y thenReturn().

En el siguiente ejemplo, se muestra cómo crear una prueba de unidades que use un objeto Context simulado en Kotlin creado con Mockito-Kotlin.

import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

private const val FAKE_STRING = "HELLO WORLD"

@RunWith(MockitoJUnitRunner::class)
class MockedContextTest {

  @Mock
  private lateinit var mockContext: Context

  @Test
  fun readStringFromContext_LocalizedString() {
    // Given a mocked Context injected into the object under test...
    val mockContext = mock<Context> {
        on { getString(R.string.name_label) } doReturn FAKE_STRING
    }

    val myObjectUnderTest = ClassUnderTest(mockContext)

    // ...when the string is returned from the object under test...
    val result: String = myObjectUnderTest.getName()

    // ...then the result should be the expected one.
    assertEquals(result, FAKE_STRING)
  }
}

Para obtener más información sobre el uso del framework de Mockito, consulta la referencia de la API de Mockito y la clase SharedPreferencesHelperTest en el código de muestra. También puedes probar el codelab de pruebas de Android.

Error: "Método… no ficticio"

La biblioteca de Android simulable arroja una excepción si intentas acceder a cualquiera de sus métodos con el mensaje Error: "Method ... not mocked.

Si las excepciones que se arrojan son problemáticas para tus pruebas, puedes cambiar el comportamiento para que los métodos devuelvan un valor nulo o cero, según el tipo de datos que se devuelve. Para ello, agrega la siguiente configuración en el archivo build.gradle de nivel superior de tu proyecto en Groovy:

android {
  ...
  testOptions {
    unitTests.returnDefaultValues = true
  }