使用伪造来测试响应流
通过亚当麦克尼尔公司

与模型不同,假实施使我们完全控制我们在单元测试中的依赖关系,使我们能够准确地彻底地测试反应性流动。在Ok188bet金宝搏官网cupid,我们经常使用Mockito.用于在我们的JUnit测试中使用依赖项的模拟库。这允许我们轻松模拟某些方法的返回值,或者验证在我们的依赖项之一上调用方法,但它可以提供并发症作为所测试部件之间的集成,并且其依赖关系变得更加复杂。
在这篇文章中,我们将介绍使用mocking库所发现的一个限制,并讨论如何通过使用我们自己的伪实现来克服这个限制。让我们先为这个问题做好准备。
设置舞台
在我们讨论这个问题之前,让我们确保我们理解了被测试的组件。我们将看到一个ViewModel,它负责发出网络请求并显示一些结果。
下面是ViewModel的代码片段,但是如果你想看到相关的类,你可以在这个要点.注意,在这个ViewModel中,一旦它被创建,我们就会在初始化
方法:
类ProfileViewModel (
userid:string,
存储库:ProfileRepository,
backgroundscheduler:scheduler = schedulers.io(),
Mainscheduler:Scheduler = A188博金宝电子体育频道ndroidschedulers.mainthread()
):视图模型(){
private val _state = mutableLiveData ()
val state: LiveData = _state
init {
_state。值= ProfileViewState.loading ()
存储库
.fetchProfile(标识)
.subscribeOn (backgroundScheduler)
.observeOn (mainScheduler)
。订阅(
{用户 - >
_state.value = profileviewstate.success(用户)
},
{错误 - >
_state.value = profileviewstate.Error(错误)
}
)
}
}
一旦我们的视图创建,我们将向我们的LiveData发出加载状态。然后,如果呼叫成功或失败,我们将请求配置文件,并发布新的ProfileViewState。
这是我们在测试中的组件所需要的一切。接下来我们可以测试它。
测试ViewModel
我们将从一个积极的用例测试开始,以确保当我们请求用户时,先发出加载状态,然后发出数据状态。让我们看看这个测试是什么样的:
类ProfileViewModelTest {
@Test
有趣的loadprofile(){
val testUser = User(userid = "123")
var mockRepository = Mockito.mock(ProfileRepository::class.java)
每当(mockrepository.fetchprofile(anystring()))
.thenreturn(single.just(testuser))
Val ViewModel = ProfileViewModel(
userid =“123”,
存储库= MockRepository,
backgroundscheduler = schedulers.trampoline(),
Mainscheduler = schedulers.trampoline()
)
val observedStates = viewModel.state.testObserver().observedValues . value
Assertthat(观察到的静音).isequalto(2)
val firstState = observedStates[0]
val secondState = observedStates[1]
为了(firstState.loading) .isTrue ()
为了(secondState.data) .isEqualTo (testUser)
}
}
如果您想看到实施.testObserver ()
你可以在这个要点.
测试失败了
令我们吃惊的是,这次考试竟然会失败!我们正在模拟一个成功的数据请求,所以我们应该期望observedStates
有两个条目:一个用于加载状态,一个用于成功数据状态。在运行测试时,第一个断言失败。我们的测试表明observedStates.size
是一个,值是数据状态。
换句话说,我们的测试无法验证发生加载状态。
发生了什么事?
让我们考虑一下单元测试与实际代码有什么不同。在我们的单元测试中,我们通过了Scheduler.trampoline ()
来自rxjava,有助于使网络请求运行好像是顺序。就此测试而言,一旦创建了ViewModel,就好像网络请求即时成功。
然后,在创建我们的视图后,我们在测试观察者ViewModel.State.
已经处于加载数据状态。这意味着加载状态发生的时间太久远了——我们无法在LiveData创建之前观察到它,因此我们无法验证加载状态是否发生过。
这种复杂性是由模拟库引起的,它告诉模拟库立即返回信息。相反,我们可以创建我们自己的ProfileRepository的伪实现,我们可以完全控制它,并且可以控制数据的释放,以确保我们的单元测试捕获加载状态。
创建一个假的
要创建虚假实现,我们首先创建实现我们接口的新类。请记住,我们不希望我们的假期立即返回数据,因为这会导致同样的问题。相反,由于我们正在使用rxjava,我们可以以一种使用a的方式实现我们的假行为赞许
在我们可以控制的幕后。
类FakeRepository: ProfileRepository {
private val userSubject: BehaviorSubject = BehaviorSubject.create()
override fun fetchProfile(userId: String): Single {
返回userSubject.hide () .firstOrError ()
}
}
如果你使用协程,你在这里使用的实现可能会改变,但概念是一样的:我们不想返回fetchProfile ()
马上提供信息。我们希望确保伪实现能够精确地控制数据何时被发出。
控制数据排放量
由于我们的假实施是使用的行为赞许
作为底层数据源,我们可以创建自己的公共方法,以便在需要的时候向它发出:
类FakeRepository: ProfileRepository {
// ......
fun emitUser(user: user) {
this.userSubject.onNext(用户)
}
}
更新测试以验证加载状态
既然我们已经有了一个系统,可以对数据何时从存储库发出进行细粒度控制,那么我们可以利用这个系统来准确地测试我们的加载状态。我们将遵循这个食谱:
- 创建我们的假存储库和ViewModel组件
- 由于我们的fake不会立即发射数据,我们将能够验证我们处于加载状态。
- 我们可以控制假实现来发射数据。
- 最后,我们可以验证ViewModel现在处于已加载数据状态。
类ProfileViewModelTest {
@Test
有趣的loadprofile(){
val testUser = User(userid = "123")
//使用假代替mockito
val fakeRepository = fakeRepository ()
//创建我们的ViewModel
Val ViewModel = ProfileViewModel(
userid =“123”,
存储库= fakerepository,
backgroundscheduler = schedulers.trampoline(),
Mainscheduler = schedulers.trampoline()
)
val testObserver = viewModel.state.testObserver()
val观察到= testobserver.observedValues
//验证我们只有加载状态
为了(observedStates.size) .isEqualTo (1)
val firstState = observedStates[0]
为了(firstState.loading) .isTrue ()
//发送数据,验证我们移动到
//加载数据状态
fakeRepository.emitUser (testUser)
Assertthat(观察到的静音).isequalto(2)
val secondState = observedStates[1]
为了(secondState.data) .isEqualTo (testUser)
}
}
回顾
mock库为我们的Android单元测试提供了一个快速创建依赖项的解决方案,但代价是对控制这些依赖项的行为有限制。188博金宝电子体育频道通过利用存在于代码库中的接口和我们自己的虚假实现,我们可以完全控制依赖关系,我们可以使用它来控制响应流的数据排放,从而彻底地对组件进行单元测试。
我希望这对你有帮助!如果你有其他使用fake和mock的例子,请告诉我推特.
有兴趣为Okcupid工作吗?188bet金宝搏官网我们正在招聘!
最初发表在https://tech.188bet金宝搏官网okcupid.com2020年6月12日。