Android架构组件GithubBrowserSample单元测试理解
大家好,我正在尝试使用Kotlin学习单元测试。我的代码确实非常相似,但我无法让他们的一个更基本的测试工作(我真的不理解它) 我的主要问题是Android架构组件GithubBrowserSample单元测试理解,android,unit-testing,kotlin,mockito,android-architecture-components,Android,Unit Testing,Kotlin,Mockito,Android Architecture Components,大家好,我正在尝试使用Kotlin学习单元测试。我的代码确实非常相似,但我无法让他们的一个更基本的测试工作(我真的不理解它) 我的主要问题是sendResultToUI()测试到底在做什么 @Test public void sendResultToUI() { MutableLiveData<Resource<User>> foo = new MutableLiveData<>(); when(userRepository.loadUser(
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);
}
所以我猜onChanged
是用空值调用的。我真的不明白这里到底发生了什么-在步骤c中调用setLogin()
会触发用户switchMap live data,而用户switchMap live data又会调用userrepository.loadUser()
,因此应该调用观察者getUser()
,但我们要求验证相反的情况(它从未被调用)。之后,调用loadUser()返回我们在a)中指定的foo。也许,如果有人能至少向我解释一下测试,我可能会理解我自己的代码
编辑:这是我自己当前的单元测试,类和模型已经更改,但据我所知,实际代码是相同的(我知道这可能更简洁,稍后会担心!)
我假设使用空值触发观察器的原因正是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)
val foo=MutableLiveData()
//调用callServerLoginRepo()时,返回foo live数据
`当`(interactior.callServerLoginRepo(电子邮件、密码))。然后返回(foo)
//观察会员实时数据
val observer:observer=mock()
loginViewModel.member.ObserveForver(观察员)
//Fire SetLoginRedentials,并确保它没有触及我们观察到的“成员”实时数据
loginViewModel.setLoginCredentials(电子邮件、密码)
验证(观察者,从不())。一旦更改(任何())
//创建一个成功的foo用户,并设置其值
val fooouser=TestUtil.createMember(电子邮件)
val fooValue=Resource.success(fooUser)
foo.value=fooValue
//确保此设置确实触发了我们的实时数据
验证(观察者).onChanged(fooValue)
重置(观察员)
您正确理解了测试。如果你展示你的代码,每个人都会更清楚地帮助你
但让我猜猜:您正在尝试将java代码转换为kotlin代码。在某些情况下,代码中可能有Mockito.any()来模拟行为或内部验证表达式。
不可能仅将any()用于kotlin。关于如何解决此问题,有一条线索:
您对测试操作的评估大致正确,但这并不一定意味着
onChanged
被调用为null
为了详细介绍我的经验,Kotlin中有一组Mockito扩展,它们提供了与Kotlin语言更好的兼容性,并改进了测试语法
我们使用这些,我推荐它们。然而,我们发现,如果我们不小心进口,我们进口:
import org.mockito.ArgumentMatchers.any
而不是:
import com.nhaarman.mockito_kotlin.any
因此,使用一组混合的Java类和Kotlin扩展,我们可以看到ArgumentMatchers.any(T::class.Java)不能为null,这是您注意到的错误
鉴于您使用的是MockitoKotlinHelpers
,您的情况可能非常相似。谢谢Shamm,所以我确实准备了MockitoKotlinHelpers.kt
,并尝试了我自己的any()
,看起来与您的非常相似:inline fun any(type:Class):T=Mockito.any(T::Class.java)
,但这两种方法都没有成功。我一定是做错了什么,我将添加我当前的测试,看看您是否发现了任何东西。首先,您不需要将“any(Resource::class.java)转换为Resource”,如果没有转换,则会出现编译错误:类型推断失败。预期类型不匹配:推断类型为Resource,但为Resource?可能我误解了任何(类)
的目的。fun any():T=Mockito.any()
实际上是kotlin的安全等价物吗?我认为这不是因为java代码调用了Mockito.any(Class)
得到了它。问题是您正在使用泛型作为类型。因此,您可以使用类型参数,只使用MockitoKotlinHelpers.kt中的任何()即可,这样您就失去了我认为并不重要的类型检查。或者你可以在非泛型类中包装泛型。谢谢Shamm,我认为nhaarman的mockito kotlin库是最安全的方法,因为它包含了其他定制的函数。关于vs Mockito vs MockitoKotlinHelpers文件有很多混淆,我怀疑Kotlin中的单元测试很快就会改变!再次感谢,我将添加一个edit+UnderstandingTanks Rob,我尝试了nhaarman的lib,但似乎没有any(Class)
变量,这是这个函数所需要的,只是一个空安全any()
,它似乎与MockitoKotlinHelpers.kt文件中的相同啊,很抱歉,我现在得到它是nhaarman的any()
实现尝试Mockito.any(T::class.java)
如果为空,它将创建一个安全的非空引用……为了更清楚一点,any()
匹配任何非空值。还有anyOrNull()
显然也与null匹配。Matchers有点违反直觉,当你进入它时,你可能会发现any()
实际上匹配的东西不是你的类。对于所需的类型匹配isA()
。有趣啊!谢谢,罗布,这是非常有用的。为了增加混淆或澄清,我认为自Mockito 2以来,any
的类变量现在可能是安全的,但我可能错了:
@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)
}
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)
inline fun <reified T : Any> any() = Mockito.any(T::class.java) ?: createInstance<T>()
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)