Android Studio 3.5.3
Kotlin 1.3
J'essaie de tester du code simple mais je reçois toujours l'exception suivante:
IllegalStateException: gsonWrapper.fromJson<Map…ring, String>>() {}.type) must not be null
J'utilise l'espion et je me moque du retour afin qu'il renvoie un null. Comme je veux tester le chemin d'erreur.
Je ne sais pas si je fais quelque chose de mal avec mon tronçonnage ou non. Mais ne semble pas résoudre cette exception.
Utiliser une classe wrapper pour encapsuler l'implémentation gson et l'espionner dans le 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);
}
}
Implémentation de ma classe en cours de 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
}
La classe de test réelle, en gardant tout 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
}
}
Merci beaucoup pour toutes suggestions,
Cela dépend de ce que vous voulez réaliser. Voulez-vous autoriser MoviePresenterImp.serializeStringToMap
pour retourner null
? Pour le moment ce n'est pas possible et c'est ce que vous testez dans votre test unitaire:
que se passera-t-il quand gsonWrapper.fromJson
renvoie null
?
serializeStringToMap
lèvera une exception car son type de retour est déclaré non nullable (Kotlin ajoute un null-check sous le capot).
En réalité,spyGsonWrapper.fromJson
renvoie uniquement null
si gson.fromJson
renvoie null
. Selon les documents Java Java de Gson, cela ne peut se produire que si l'argument json
est null
(si json
n'est pas valide, la méthode lance JsonSyntaxException
). Vous devez donc:
json
est null
dans le spyGsonWrapper.fromJson
et lancez IllegalArgumentException si c'est le cas. Cela garantira que la méthode ne renvoie jamais null
(btw. Vous pouvez ajouter un @NotNull
annotation, voir annotations-nullité ). Vous pouvez conserver serializeStringToMap
tel quel, mais vous devez modifier le test, car cela n'a plus de sens.null
au lieu de lever une exception, vous devez modifier MoviePresenterImp.serializeStringToMap comme suggéré par @ duongdt3Voici un exemple de 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
// ...
}
}
J'ai trouvé des problèmes ici:
Vous devez utiliser la carte nullable? au lieu d'une carte non nulle dans MoviePresenterImp (code Kotlin), car dans la classe Unit Test, vous espionnez gsonWrapper et forcez la méthode 'spyGsonWrapper.fromJson' à retourner null.
Ça va maintenant.
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
}
Avec votre configuration, vous recherchez emptyMap()
au lieu de null
whenever(spyGsonWrapper.fromJson<Map<String, String>>("{\"movie\":\"movieId\"}", mapType))
.thenReturn(emptyMap())
Ce sera conforme à la signature, car il est non nul
fun serializeStringToMap(ccpaStatus: String): Map<String, String>
Il entrera également le bloc else dans l'appel movieMap.getOrElse()
.