Android架构组件GithubBrowserSample单元测试理解

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(

大家好,我正在尝试使用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);
}
所以我猜
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)