大家好,我正在尝试使用Google的github浏览器示例作为基础来学习Kotlin单元测试。我的代码确实非常相似,但我无法使其中一个更基本的测试工作(而且我真的不理解它)。
我的主要问题是sendResultToUI()
测试具体在做什么,
@Test
public void sendResultToUI() {
MutableLiveData<Resource<User>> foo = new MutableLiveData<>();
when(userRepository.loadUser("foo")).thenReturn(foo);
Observer<Resource<User>> observer = mock(Observer.class);
userViewModel.getUser().observeForever(observer);
userViewModel.setLogin("foo");
verify(observer, never()).onChanged(any(Resource.class));
User fooUser = TestUtil.createUser("foo");
Resource<User> fooValue = Resource.success(fooUser);
foo.setValue(fooValue);
verify(observer).onChanged(fooValue);
reset(observer);
}
据我所知,它的意思是:
a) 当调用
loadUser("foo")
时,不执行该函数,而只返回一个名为 foo 的新活动数据对象。b) 观察
userViewModel.getUser()
活动数据。c) 调用
setLogin("foo")
,这会触发活动数据并调用 loadUser("foo")
。d) 验证我们之前创建的对
getUser()
的观察者从未被任何 Resource 实例触发过。e) 创建一个成功的 foo 用户,验证设置其值是否会触发
getUser()
观察者。因此,如果所有这些大致正确,我的问题在于步骤 d)。我的代码抛出了一个异常。
java.lang.IllegalStateException: ArgumentMatchers.any(T::class.java) must not be null
所以我猜测
onChanged
被调用时传入了null值。我真的不知道到底发生了什么 - 在步骤c)中调用setLogin()
触发了用户switchMap live data,进而调用userRepositiory.loadUser()
,这样应该会调用观察者getUser()
,但我们要求验证相反的情况(它从未被调用)。毕竟调用loadUser()返回了我们在a)中指定的foo。也许如果有人能解释一下这个测试,我就可以理解自己的代码了!
编辑:这是我的当前单元测试,类和模型已经改变,但据我所知实际代码是相同的(我意识到这可能更简洁,将来会担心这个问题!)
@Test
fun `send result to UI`(){
val foo = MutableLiveData<Resource<Member>>()
`when`(interactor.callServerLoginRepo(email, password)).thenReturn(foo)
val observer: Observer<Resource<Member>> = mock()
loginViewModel.member.observeForever(observer)
loginViewModel.setLoginCredentials(email, password)
verify<Observer<Resource<Member>>>(observer, never()).onChanged(any(Resource::class.java) as Resource<Member>)
val fooUser = TestUtil.createMember(email)
val fooValue = Resource.success(fooUser)
foo.setValue(fooValue)
verify<Observer<Resource<Member>>>(observer).onChanged(fooValue)
reset<Observer<Resource<Member>>>(observer)
}
MockitoHelpers.kt
fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
错误也略有不同:
kotlin.TypeCastException: null cannot be cast to non-null type app.core.sdk.data.remote.response.Resource<app.core.sdk.data.model.db.Member>
at app.core.sdk.ui.login.LoginViewModelTest.send result to UI(LoginViewModelTest.kt:114)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
编辑2,最终代码:
看起来我的主要问题在于对不同的any()
函数的混淆,基本上我需要一个空安全调用any()
,最好能指定匹配类。Mockito-Kotlin库看起来是目前最安全的路线,因为我需要一个any
函数,它将回退到我指定的类,并且我认为他的版本可以做到这一点:
inline fun <reified T : Any> any() = Mockito.any(T::class.java) ?: createInstance<T>()
我猜测观察者被使用空值触发的原因只是
Mockito.any()
函数的作用,这也是 Kotlin 抛出异常的地方。 val foo = MutableLiveData<Resource<Member>>()
//When callServerLoginRepo() is called, return foo live data
`when`(interactor.callServerLoginRepo(email, password)).thenReturn(foo)
//Observe member live data
val observer: Observer<Resource<Member>> = mock()
loginViewModel.member.observeForever(observer)
//Fire setLoginCredentials, and make sure it didn't touch our observed 'member' live data
loginViewModel.setLoginCredentials(email, password)
verify(observer, never()).onChanged(any())
//Create a successful foo user, and set it's value
val fooUser = TestUtil.createMember(email)
val fooValue = Resource.success(fooUser)
foo.value = fooValue
//Ensure setting this did indeed trigger our live data
verify(observer).onChanged(fooValue)
reset(observer)
any(Class<T>)
的目的。fun <T> any(): T = Mockito.any<T>()
是否真的是Kotlin安全等效的呢?我认为不是,因为Java代码调用了Mockito.any<T>(Class<T>)
。 - Daniel WilsonMockitoKotlinHelpers.kt
中的任何(),然后您将失去类型检查,我认为这并不是很关键。或者,您可以将泛型包装在非泛型类中。 - Shamm