ant*_*009 8 android unit-testing mockito kotlin
Android Studio 3.5.3
Kotlin 1.3
Run Code Online (Sandbox Code Playgroud)
我正在尝试测试一些简单的代码,但我不断收到以下异常:
IllegalStateException: gsonWrapper.fromJson<Map…ring, String>>() {}.type) must not be null
Run Code Online (Sandbox Code Playgroud)
我正在使用间谍并嘲笑返回,因此它将返回空值。因为我想测试错误路径。
不确定我的存根是否有问题。但似乎无法解决此异常。
使用包装类包装 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);
}
}
Run Code Online (Sandbox Code Playgroud)
我正在测试的类的实现
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
}
Run Code Online (Sandbox Code Playgroud)
实际的测试类,只是保持一切简单
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
}
}
Run Code Online (Sandbox Code Playgroud)
非常感谢您的任何建议,
我在这里发现了问题:
您必须使用可为空的 Map?而不是 MoviePresenterImp(Kotlin 代码)中的非空 Map,因为在单元测试类中,您监视 gsonWrapper,并且强制方法 'spyGsonWrapper.fromJson' 返回 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
}
Run Code Online (Sandbox Code Playgroud)
这取决于您想要实现的目标。您想允许MoviePresenterImp.serializeStringToMap
返回null
吗?目前这是不可能的,这就是您在单元测试中测试的内容:
gsonWrapper.fromJson
返回时会发生什么null
?
serializeStringToMap
会抛出异常,因为它的返回类型被声明为不可空(Kotlin 在底层添加了空检查)。
事实上,spyGsonWrapper.fromJson
只有 returnnull
才gson.fromJson
返回null
。根据 Gson 的 java 文档,只有当json
参数为null
(如果json
无效,方法会抛出JsonSyntaxException
)时,才会发生这种情况。所以你应该:
json
参数是否在null
其中spyGsonWrapper.fromJson
,如果是则抛出 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
// ...
}
}
Run Code Online (Sandbox Code Playgroud)