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
}
}
提案をありがとう、
それはあなたが何を達成したいかによります。 MoviePresenterImp.serializeStringToMap
がnull
を返すことを許可しますか?現時点ではそれは不可能であり、それが単体テストでテストしているものです。
gsonWrapper.fromJson
がnull
を返すとどうなりますか?
serializeStringToMap
は、戻り値の型がnull不可であると宣言されているため、例外をスローします(Kotlinは内部でnullチェックを追加します)。
実際、spyGsonWrapper.fromJson
がnull
を返す場合、gson.fromJson
はnull
のみを返します。 GsonのJava docsによると、json
引数がnull
である場合にのみ発生する可能性があります(json
が無効な場合、メソッドはJsonSyntaxException
をスローします)。
json
パラメータがspyGsonWrapper.fromJson
で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
// ...
}
}
ここで問題が見つかりました:
Null可能なマップを使用する必要がありますか?ユニットテストクラスではgsonWrapperをスパイし、メソッド 'spyGsonWrapper.fromJson'がnullを返すため、MoviePresenterImp(Kotlinコード)の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
}
セットアップでは、null
ではなくemptyMap()
を探しています
_whenever(spyGsonWrapper.fromJson<Map<String, String>>("{\"movie\":\"movieId\"}", mapType))
.thenReturn(emptyMap())
_
これはnullではないため、署名に準拠します
_fun serializeStringToMap(ccpaStatus: String): Map<String, String>
_
また、movieMap.getOrElse()
呼び出し内でelse-blockに入ります。