【问题标题】:Getting a null pointer exception when mocking and spying in a test class在测试类中模拟和监视时获取空指针异常
【发布时间】:2020-04-07 03:40:18
【问题描述】:
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
    }
}

非常感谢您的任何建议,

【问题讨论】:

    标签: android unit-testing kotlin mockito


    【解决方案1】:

    我在这里发现了问题:

    您必须使用可为空的 Map?而不是 MoviePresenterImp (Kotlin 代码)中的非 null Map,因为在 Unit Test 类中,您监视 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
        }
    

    【讨论】:

    • 嗨,是的。但是,我不想返回空地图,我想将其保留为non-null。我想我想要实现的是返回一个包含空内容的地图。所以当movieMap.getOrElse() 会返回一个null。这就是我试图嘲笑的。让 movieMap 包含 null 是我不知道该怎么做的事情?。
    • 其实,关于用Gson解析JSON,你应该支持null case,有时JSON文本不符合我们的预期。你有一个 null 的测试用例是个好主意。
    【解决方案2】:

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

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

    • serializeStringToMap 将抛出异常,因为它的返回类型被声明为不可为空(Kotlin 在后台添加了一个空检查)。

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

    • 检查spyGsonWrapper.fromJson中的json参数是否为null,如果是则抛出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】:

      通过您的设置,您正在寻找 emptyMap() 而不是 null

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

      这将符合签名,因为它是非空的

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

      它还将进入 movieMap.getOrElse() 调用中的 else 块。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-24
        • 1970-01-01
        • 2023-03-18
        • 2018-06-02
        • 2019-12-26
        • 1970-01-01
        • 2013-07-27
        • 1970-01-01
        相关资源
        最近更新 更多