Getting a null pointer exception when mocking and spying in a test class

I found problems here:

You must use nullable Map? instead of non-null Map in MoviePresenterImp (Kotlin code), because in Unit Test class, you spy gsonWrapper, and force method 'spyGsonWrapper.fromJson' return null.

It OK now.

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
    }

It depends on what you want to achieve. Do you want to allow MoviePresenterImp.serializeStringToMap to return null? At the moment it is not possible and that's what you are testing in your unit test:

  • what will happen when gsonWrapper.fromJson returns null?

  • serializeStringToMap will throw an exception because its return type is declared to be non-nullable (Kotlin adds a null-check under the hood).

In fact,spyGsonWrapper.fromJson only returns null if gson.fromJson returns null. According to the Gson's java docs, it may happen only if the json argument is null (if json is invalid the method throws JsonSyntaxException). So you should either:

  • check if the json parameter is null in the spyGsonWrapper.fromJson and throw IllegalArgumentException if it is. This will ensure that the method never returns null (btw. you could add a @NotNull annotation, see nullability-annotations). You can keep serializeStringToMap as it is but you need to change the test, because it makes no sense anymore.
  • if you prefer returning null instead of throwing an exception, you need to change the MoviePresenterImp.serializeStringToMap as suggested by @duongdt3

Here is an example test:

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

With your setup you're looking for emptyMap() instead of null

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

This will comply with the signature, as it's non-null

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

Also it'll enter the else-block within the movieMap.getOrElse() call.