Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Android Studio 3.5.3
Kotlin 1.3

I am trying to test some simple code but I keep getting the following exception:

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

I am using the spy and mocking the return so it will return a null. As I want to test the error path.

Not sure if I am doing something wrong with my stubbing or not. But can't seem to resolve this exception.

Using a wrapper class to wrap the gson implementation and spying on this in the test

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

Implementation of my class that is under test

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
}

The actual test class, just keeping everything simple

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

Many thanks for any suggestions,

like image 694
ant2009 Avatar asked Dec 13 '19 16:12

ant2009


2 Answers

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
    }
like image 129
duongdt3 Avatar answered Oct 26 '22 10:10

duongdt3


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
        // ...
    }
}
like image 32
Mafor Avatar answered Oct 26 '22 08:10

Mafor