web-dev-qa-db-ja.com

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

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

提案をありがとう、

8
ant2009

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

  • gsonWrapper.fromJsonnullを返すとどうなりますか?

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

実際、spyGsonWrapper.fromJsonnullを返す場合、gson.fromJsonnullのみを返します。 GsonのJava docsによると、json引数がnullである場合にのみ発生する可能性があります(jsonが無効な場合、メソッドはJsonSyntaxExceptionをスローします)。

  • jsonパラメータがspyGsonWrapper.fromJsonnullであるかどうかを確認し、そうである場合はIllegalArgumentExceptionをスローします。これにより、メソッドが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
        // ...
    }
}
3
Mafor

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

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

セットアップでは、nullではなくemptyMap()を探しています

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

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

_fun serializeStringToMap(ccpaStatus: String): Map<String, String>
_

また、movieMap.getOrElse()呼び出し内でelse-blockに入ります。

2
tynn