テストクラスをモックしてスパイするときにnullポインタ例外が発生する


8
Android Studio 3.5.3
Kotlin 1.3

いくつかの簡単なコードをテストしようとしていますが、次の例外が発生し続けます。

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

私はスパイを使用して、戻り値をあざけるので、nullを返します。エラーパスをテストしたいので。

私が私のスタブで何か悪いことをしているかどうかわからない。しかし、この例外を解決できないようです。

ラッパークラスを使用してgson実装をラップし、テストでこれをスパイする

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

テスト中の私のクラスの実装

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
}

すべてを単純に保つ実際のテストクラス

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

提案をありがとう、

回答:


3

それはあなたが何を達成したいかによります。MoviePresenterImp.serializeStringToMap返品を許可しnullますか?現時点ではそれは不可能であり、それがあなたが単体テストでテストしているものです:

  • gsonWrapper.fromJson戻るとどうなりますnullか?

  • serializeStringToMap 戻り値の型がnull可能ではないと宣言されているため、例外がスローされます(Kotlinは内部でnullチェックを追加します)。

実際、spyGsonWrapper.fromJsonが返されるnull場合にのみ返されgson.fromJsonますnull。GsonのJavaドキュメントによると、これはjson引数がnulljson無効な場合、メソッドがスローするJsonSyntaxException)の場合にのみ発生する可能性があります。したがって、次のいずれかを行う必要があります。

  • jsonパラメータがnullにあるかどうかを確認し、ある場合はspyGsonWrapper.fromJsonIllegalArgumentExceptionをスローします。これにより、メソッドが戻らないことが保証されますnull(ところで、@NotNull注釈を追加できます。nullability-annotationsを参照してください)。あなたは保つことができますserializeStringToMapしてことはますが、もう意味がないので、テストを変更する必要があります。
  • null例外をスローするのではなく戻る場合は、@ duongdt3の提案に従ってMoviePresenterImp.serializeStringToMapを変更する必要があります。

ここにテストの例があります:

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

6

ここで問題が見つかりました:

null可能なマップを使用する必要がありますか?ユニットテストクラスではgsonWrapperをスパイし、メソッド 'spyGsonWrapper.fromJson'がnullを返すため、MoviePresenterImp(Kotlinコード)のnull以外のマップの代わりに。

今は大丈夫です。

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
    }

こんにちは、はい、うまくいきました。しかし、私はnullマップを返したくありませんnon-null。それをとして保持したいと思います。私が達成しようとしているのは、コンテンツがnullのマップを返すことだと思います。したがって、いつmovieMap.getOrElse()nullが返されます。それは私が模倣しようとしていたものです。movieMapにnullが含まれているのは、どうすればよいかわからない場合です。
ant2009

1
実際、Gsonを使用したJSONの解析については、nullケースをサポートする必要があります。JSONテキストが期待と一致しない場合があります。nullのテストケースがあるのは良い考えです。
duongdt3

2

あなたのセットアップであなたが探しているのではemptyMap()なくnull

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

これはnullではないため、署名に準拠します

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

また、movieMap.getOrElse()コール内でelse-blockに入ります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.