Beim Verspotten und Spionieren in einer Testklasse wird eine Nullzeigerausnahme angezeigt

8
Android Studio 3.5.3
Kotlin 1.3

Ich versuche, einfachen Code zu testen, erhalte jedoch immer wieder die folgende Ausnahme:

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

Ich benutze den Spion und verspotte die Rückgabe, damit sie eine Null zurückgibt. Da möchte ich den Fehlerpfad testen.

Ich bin mir nicht sicher, ob ich mit meinem Stubbing etwas falsch mache oder nicht. Kann diese Ausnahme jedoch nicht beheben.

Verwenden einer Wrapper-Klasse zum Umschließen der gson-Implementierung und Ausspionieren dieser im Test

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

Implementierung meiner Klasse, die getestet wird

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
}

Die eigentliche Testklasse, einfach alles einfach halten

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

Vielen Dank für alle Vorschläge,

ant2009
quelle

Antworten:

3

Es kommt darauf an, was Sie erreichen wollen. Möchten Sie die MoviePresenterImp.serializeStringToMapRückkehr erlauben null? Im Moment ist es nicht möglich und das ist es, was Sie in Ihrem Unit-Test testen:

  • Was passiert bei der gsonWrapper.fromJsonRückkehr null?

  • serializeStringToMap löst eine Ausnahme aus, da der Rückgabetyp als nicht nullbar deklariert ist (Kotlin fügt unter der Haube eine Nullprüfung hinzu).

Tatsächlich wird spyGsonWrapper.fromJsonnur zurückgegeben, nullwenn gson.fromJsonzurückgegeben wird null. Laut den Java-Dokumenten des Gson kann dies nur passieren, wenn das jsonArgument lautet null(wenn jsones ungültig ist, wird die Methode ausgelöst JsonSyntaxException). Sie sollten also entweder:

  • Überprüfen Sie, ob sich der jsonParameter nullin der befindet, spyGsonWrapper.fromJsonund lösen Sie gegebenenfalls die IllegalArgumentException aus. Dadurch wird sichergestellt, dass die Methode niemals zurückgegeben wird null(übrigens können Sie eine @NotNullAnnotation hinzufügen , siehe Nullability-Annotationen ). Sie können so bleiben, serializeStringToMapwie es ist, aber Sie müssen den Test ändern, da er keinen Sinn mehr ergibt.
  • Wenn Sie lieber zurückkehren als nulleine Ausnahme auslösen möchten, müssen Sie die MoviePresenterImp.serializeStringToMap wie von @ duongdt3 vorgeschlagen ändern

Hier ist ein Beispieltest:

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
quelle
6

Ich habe hier Probleme gefunden:

Sie müssen nullable Map verwenden? Anstelle einer Nicht-Null-Zuordnung in MoviePresenterImp (Kotlin-Code), da Sie in der Unit-Test-Klasse gsonWrapper ausspionieren und die Methode 'spyGsonWrapper.fromJson' erzwingen, dass null zurückgegeben wird.

Es ist jetzt OK.

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
quelle
Hallo, ja das hat funktioniert. Ich möchte jedoch keine Null-Map zurückgeben und möchte sie als behalten non-null. Ich denke, was ich erreichen möchte, ist die Rückgabe einer Karte mit null Inhalten. Wann movieMap.getOrElse()wird also eine Null zurückgegeben? Das habe ich verspottet. Ich bin mir nicht sicher, wie ich die MovieMap mit einer Null versehen soll.
Ant2009
1
Wenn Sie JSON mit Gson analysieren, sollten Sie den Fall null unterstützen. Manchmal entspricht JSON-Text nicht unseren Erwartungen. Sie haben einen Testfall für null ist eine gute Idee.
duongdt3
2

Mit Ihrem Setup suchen Sie emptyMap()stattnull

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

Dies entspricht der Signatur, da sie nicht null ist

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

Außerdem wird der else-Block innerhalb des movieMap.getOrElse()Anrufs eingegeben .

tynn
quelle