使用伪造来测试响应流

通过亚当麦克尼尔公司

与模拟不同,伪实现让我们完全控制单元测试中的依赖关系,允许我们准确而彻底地测试响应流。在Ok188bet金宝搏官网Cupid上,我们经常使用the5库,用于创建要在JUnit测试中使用的依赖项的模拟。这允许我们轻松地模拟某些方法的返回值,或者验证某个依赖项上调用了某个方法,但随着被测试组件与其依赖项之间的集成变得更加复杂,这也会带来复杂性。

在这篇文章中,我们将介绍使用mocking库所发现的一个限制,并讨论如何通过使用我们自己的伪实现来克服这个限制。让我们先为这个问题做好准备。

奠定了基础

在我们讨论这个问题之前,让我们确保我们理解了被测试的组件。我们将看到一个ViewModel,它负责发出网络请求并显示一些结果。

下面是ViewModel的代码片段,但是如果你想看到相关的类,你可以在这个要点.注意,在这个ViewModel中,一旦它被创建,我们就会在初始化方法:

类ProfileViewModel (
用户标识:字符串,
存储库:ProfileRepository,
Scheduler: Scheduler = Schedulers.io(),
mainScheduler = AndroidSche188博金宝电子体育频道dulers.mainThread()
):视图模型(){
private val _state = MutableLiveData()
val state: LiveData = _state

init {
_state。值= ProfileViewState.loading ()

存储库
.fetchProfile(标识)
.subscribeOn (backgroundScheduler)
.observeOn (mainScheduler)
.subscribe (
{用户- >
_state。值= ProfileViewState.success(用户)
},
{错误- >
_state。值= ProfileViewState.error(错误)



ViewModel一创建,我们就会向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(
用户id = " 123 ",
库= mockRepository,
backgroundScheduler = Schedulers.trampoline (),
mainScheduler = Schedulers.trampoline ()


val observedStates = viewModel.state.testObserver().observedValues . value
为了(observedStates.size) .isEqualTo (2)

val firstState = observedStates[0]
val secondState = observedStates[1]
为了(firstState.loading) .isTrue ()
为了(secondState.data) .isEqualTo (testUser)

如果你想看看.testObserver ()你可以在这个要点

测试失败

令我们吃惊的是,这次考试竟然会失败!我们正在模拟一个成功的数据请求,所以我们应该期望observedStates有两个条目:一个用于加载状态,一个用于成功数据状态。在运行测试时,第一个断言失败。我们的测试表明observedStates.size为1,值为数据状态。

换句话说,我们的测试无法验证加载状态是否发生。

发生了什么事?

让我们考虑一下单元测试与实际代码有什么不同。在我们的单元测试中,我们通过了Scheduler.trampoline ()从RxJava,这有助于使网络请求运行,好像它是顺序的。就这个测试而言,就好像一旦创建了ViewModel,网络请求就立即成功了。

然后,在我们的ViewModel创建之后,我们在ViewModel.state已经处于加载数据状态。这意味着加载状态发生的时间太久远了——我们无法在LiveData创建之前观察到它,因此我们无法验证加载状态是否发生过。

这种复杂性是由模拟库引起的,它告诉模拟库立即返回信息。相反,我们可以创建我们自己的ProfileRepository的伪实现,我们可以完全控制它,并且可以控制数据的释放,以确保我们的单元测试捕获加载状态。

创建一个假的

要创建一个伪实现,首先要创建一个实现接口的新类。请记住,我们不希望我们的假数据立即返回数据,因为这只会导致同样的问题。相反,因为我们使用的是RxJava,所以我们可以用BehaviorSubject在我们可以控制的幕后。

类FakeRepository: ProfileRepository {
private val userSubject: BehaviorSubject = BehaviorSubject.create()

override fun fetchProfile(userId: String): Single {
返回userSubject.hide () .firstOrError ()

如果你使用协程,你在这里使用的实现可能会改变,但概念是一样的:我们不想返回fetchProfile ()马上提供信息。我们希望确保伪实现能够精确地控制数据何时被发出。

控制数据排放

因为我们的伪实现使用了BehaviorSubject作为底层数据源,我们可以创建自己的公共方法,以便在需要的时候向它发出:

类FakeRepository: ProfileRepository {
/ /……

fun emitUser(user: user) {
this.userSubject.onNext(用户)

更新测试以验证加载状态

既然我们已经有了一个系统,可以对数据何时从存储库发出进行细粒度控制,那么我们可以利用这个系统来准确地测试我们的加载状态。我们将遵循这个食谱:

  1. 创建我们的假存储库和ViewModel组件
  2. 由于我们的fake不会立即发射数据,我们将能够验证我们处于加载状态。
  3. 我们可以控制假实现来发射数据。
  4. 最后,我们可以验证ViewModel现在处于已加载数据状态。
类ProfileViewModelTest {

@Test
乐趣loadProfile () {
val testUser = User(userid = "123")

//使用假的而不是mockito
val fakeRepository = fakeRepository ()

//创建视图模型
val viewModel = ProfileViewModel(
用户id = " 123 ",
库= fakeRepository,
backgroundScheduler = Schedulers.trampoline (),
mainScheduler = Schedulers.trampoline ()


val testObserver = viewModel.state.testObserver()
val observedStates = testObserver.observedValues . value

//验证我们只有加载状态
为了(observedStates.size) .isEqualTo (1)
val firstState = observedStates[0]
为了(firstState.loading) .isTrue ()

//发送数据,验证我们移动到
//加载数据状态
fakeRepository.emitUser (testUser)
为了(observedStates.size) .isEqualTo (2)
val secondState = observedStates[1]
为了(secondState.data) .isEqualTo (testUser)

回顾

mock库为我们的Android单元测试提供了一个快速创建依赖项的解决方案,但代价是对控制这些依赖项的行为有限制。188博金宝电子体育频道通过利用存在于代码库中的接口和我们自己的虚假实现,我们可以完全控制依赖关系,我们可以使用它来控制响应流的数据排放,从而彻底地对组件进行单元测试。

我希望这对你有帮助!如果你有其他使用fake和mock的例子,请告诉我推特

有兴趣为OkCupid工作吗?188bet金宝搏官网我们正在招聘

最初发表在https://tech.188bet金宝搏官网okcupid.com2020年6月12日。

188bet金宝搏官网OkCupid科技博客

阅读来自OkCupid工程团队的故事,每天连188bet金宝搏官网接着数百万人

188bet金宝搏官网OkCupid科技博客

188bet金宝搏官网OkCupid的工程团队负责每天为数百万人配对。在OkCupid科技博客上阅读他们的故事188bet金宝搏官网

188bet金宝搏官网OkCupid科技博客

188bet金宝搏官网OkCupid的工程团队负责每天为数百万人配对。在OkCupid科技博客上阅读他们的故事188bet金宝搏官网