Obtener una excepción de puntero nulo al burlarse y espiar en una clase de prueba

8
Android Studio 3.5.3
Kotlin 1.3

Estoy tratando de probar un código simple pero sigo recibiendo la siguiente excepción:

IllegalStateException: gsonWrapper.fromJson<Map…ring, String>>() {}.type) must not be null

Estoy usando el espía y burlando el retorno para que devuelva un valor nulo. Como quiero probar la ruta de error.

No estoy seguro de si estoy haciendo algo mal con mi tropezón o no. Pero parece que no puede resolver esta excepción.

Usar una clase contenedora para ajustar la implementación de gson y espiar esto en la prueba

public class GsonWrapper implements IGsonWrapper {

    private Gson gson;

    public GsonWrapper(Gson gson) {
        this.gson = gson;
    }

    @Override public <T> T fromJson(String json, Type typeOf) {
        return gson.fromJson(json, typeOf);
    }
}

Implementación de mi clase que está bajo prueba

class MoviePresenterImp(
        private val gsonWrapper: IGsonWrapper) : MoviePresenter {

    private companion object {
        const val movieKey = "movieKey"
    }

    override fun saveMovieState(movieJson: String) {
            val movieMap = serializeStringToMap(movieJson)

            when (movieMap.getOrElse(movieKey, {""})) {
                /* do something here */
            }
    }

    // Exception return from this method
    private fun serializeStringToMap(ccpaStatus: String): Map<String, String> =
            gsonWrapper.fromJson<Map<String, String>>(ccpaStatus, object : TypeToken<Map<String, String>>() {}.type) // Exception
}

La clase de prueba real, simplemente manteniendo todo simple

class MoviePresenterImpTest {
    private lateinit var moviePresenterImp: MoviePresenterImp
    private val gsonWrapper: GsonWrapper = GsonWrapper(Gson())
    private val spyGsonWrapper = spy(gsonWrapper)

    @Before
    fun setUp() {
        moviePresenterImp = MoviePresenterImp(spyGsonWrapper)
    }

    @Test
    fun `should not save any movie when there is an error`() {
        // Arrange
        val mapType: Type = object : TypeToken<Map<String, String>>() {}.type
        whenever(spyGsonWrapper.fromJson<Map<String, String>>("{\"movie\":\"movieId\"}", mapType)).thenReturn(null)

        // Act
        moviePresenterImp.saveMovieState("{\"movie\":\"movieId\"}")

        // Assert here
    }
}

Muchas gracias por las sugerencias,

ant2009
fuente

Respuestas:

3

Depende de lo que quieras lograr. ¿Quieres permitir MoviePresenterImp.serializeStringToMapque regrese null? Por el momento no es posible y eso es lo que está probando en su prueba unitaria:

  • ¿Qué pasará cuando gsonWrapper.fromJsonregrese null?

  • serializeStringToMap lanzará una excepción porque su tipo de retorno se declara como no anulable (Kotlin agrega un cheque nulo debajo del capó).

De hecho, spyGsonWrapper.fromJsonsolo regresa nullsi gson.fromJsonregresa null. De acuerdo con los documentos de Java de Gson, puede ocurrir solo si el jsonargumento es null(si jsonno es válido, el método arroja JsonSyntaxException). Entonces deberías:

  • compruebe si el jsonparámetro está nullen spyGsonWrapper.fromJsony arroje IllegalArgumentException si lo está. Esto asegurará que el método nunca regrese null(por cierto, podría agregar una @NotNullanotación, ver anotaciones de nulabilidad ). Puedes mantenerserializeStringToMap tal como está, pero necesita cambiar la prueba, porque ya no tiene sentido.
  • si prefiere regresar en nulllugar de lanzar una excepción, debe cambiar MoviePresenterImp.serializeStringToMap como lo sugiere @ duongdt3

Aquí hay un ejemplo de prueba:

class MoviePresenterImpTest {

    private lateinit var moviePresenter: MoviePresenterImp
    private lateinit var spyGsonWrapper: GsonWrapper

    @Rule @JvmField
    var thrown = ExpectedException.none();

    @Before
    fun setUp() {
        spyGsonWrapper = Mockito.mock(GsonWrapper::class.java)
        moviePresenter = MoviePresenterImp(spyGsonWrapper)
    }

    @Test
    fun `should not save any movie when GsonWrapper throws an error`() {
        // Given
        Mockito.`when`(spyGsonWrapper.fromJson<Map<String, String>>(anyString(), any(Type::class.java)))
            .thenThrow(JsonSyntaxException("test"))
        // Expect
        thrown.expect(JsonSyntaxException::class.java)
        // When
        moviePresenter.saveMovieState("{\"movie\":\"movieId\"}")
    }

   // Or without mocking at all

    @Test
    fun `should not save any movie when Gson throws error`() {
        // Given
        moviePresenter = MoviePresenterImp(GsonWrapper(Gson()))
        // Expect
        thrown.expect(JsonSyntaxException::class.java)
        // When
        moviePresenter.saveMovieState("Some invalid json")
    }

    // If you want to perform additional checks after an exception was thrown
    // then you need a try-catch block

    @Test
    fun `should not save any movie when Gson throws error and `() {
        // Given
        moviePresenter = MoviePresenterImp(GsonWrapper(Gson()))
        // When
        try {
            moviePresenter.saveMovieState("Some invalid json")
            Assert.fail("Expected JsonSyntaxException")
        } catch(ex : JsonSyntaxException) {}
        // Additional checks
        // ...
    }
}
Mafor
fuente
6

Encontré problemas aquí:

¿Debes usar el mapa anulable? en lugar de un mapa no nulo en MoviePresenterImp (código Kotlin), porque en la clase de prueba unitaria, espías gsonWrapper y fuerza el método 'spyGsonWrapper.fromJson' devuelve nulo.

Está bien ahora.

fun saveMovieState(movieJson: String) {
        val movieMap = serializeStringToMap(movieJson)

        when (movieMap?.getOrElse(movieKey, { "" })) {
            /* do something here */
        }
    }

    // Exception return from this method
    private fun serializeStringToMap(ccpaStatus: String): Map<String, String>? {
        val type: Type =
            object : TypeToken<Map<String, String>>() {}.type
        return gsonWrapper.fromJson(ccpaStatus, type) // Exception
    }
duongdt3
fuente
Hola, eso funcionó. Sin embargo, no quiero devolver un mapa nulo y me gustaría mantenerlo como non-null. Creo que lo que estoy tratando de lograr es devolver un mapa que tenga contenido nulo. Entonces, cuando movieMap.getOrElse()devolverá un nulo. Eso es lo que estaba tratando de burlarme. Hacer que el movieMap contenga un valor nulo es algo que no estoy seguro de cómo hacer.
ant2009
1
En realidad, sobre analizar JSON con Gson, debe admitir el caso nulo, a veces el texto JSON no coincide con nuestras expectativas. Tienes un caso de prueba para nulo es una buena idea.
duongdt3
2

Con su configuración que está buscando en emptyMap()lugar denull

whenever(spyGsonWrapper.fromJson<Map<String, String>>("{\"movie\":\"movieId\"}", mapType))
    .thenReturn(emptyMap())

Esto cumplirá con la firma, ya que no es nula

fun serializeStringToMap(ccpaStatus: String): Map<String, String>

También ingresará el bloque else dentro de la movieMap.getOrElse()llamada.

tynn
fuente