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,
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:
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.null
instead of throwing an exception, you need to change the MoviePresenterImp.serializeStringToMap as suggested by @duongdt3Here 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
// ...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With