在测试类中进行模拟和监视时获取空指针异常

ant*_*009 8 android unit-testing mockito kotlin

Android Studio 3.5.3
Kotlin 1.3
Run Code Online (Sandbox Code Playgroud)

我正在尝试测试一些简单的代码,但我不断收到以下异常:

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

我正在使用间谍并嘲笑返回,因此它将返回空值。因为我想测试错误路径。

不确定我的存根是否有问题。但似乎无法解决此异常。

使用包装类包装 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在测试的类的实现

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
}
Run Code Online (Sandbox Code Playgroud)

实际的测试类,只是保持一切简单

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
    }
}
Run Code Online (Sandbox Code Playgroud)

非常感谢您的任何建议,

duo*_*dt3 6

我在这里发现了问题:

您必须使用可为空的 Map?而不是 MoviePresenterImp(Kotlin 代码)中的非空 Map,因为在单元测试类中,您监视 gsonWrapper,并且强制方法 'spyGsonWrapper.fromJson' 返回 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
    }
Run Code Online (Sandbox Code Playgroud)


Maf*_*for 3

这取决于您想要实现的目标。您想允许MoviePresenterImp.serializeStringToMap返回null吗?目前这是不可能的,这就是您在单元测试中测试的内容:

  • gsonWrapper.fromJson返回时会发生什么null

  • serializeStringToMap会抛出异常,因为它的返回类型被声明为不可空(Kotlin 在底层添加了空检查)。

事实上,spyGsonWrapper.fromJson只有 returnnullgson.fromJson返回null。根据 Gson 的 java 文档,只有当json参数为null(如果json无效,方法会抛出JsonSyntaxException)时,才会发生这种情况。所以你应该:

  • 检查json参数是否在null其中spyGsonWrapper.fromJson,如果是则抛出 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
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)