使用假货来测试反应性流动

经过亚当麦克尼利

Unlike mocks, fake implementations give us full control of our dependencies in unit tests, allowing us to accurately and thoroughly test reactive flows. At OkCupid, we often use theMockitolibrary for creating mocks of our dependencies to be used within our JUnit tests. This allows us to easily mock return values for certain methods, or to verify a method was called on one of our dependencies, but it can provide complications as the integration between the component under test and its dependencies become more complicated.

在这篇文章中,我们将通过一个嘲弄图书馆进行的限制,并讨论如何通过使用我们自己的假实施来实现过去这一限制。让我们首先设置问题阶段。

Setting The Stage

在我们讨论问题之前,让我们确保我们理解正在测试的组件。我们将在查看一个责任网络请求并显示一些结果的ViewModel。

下面是ViewModel的片段,但如果您想看到相关类,您可以找到它们这个小麦。请注意,在此ViewModel中,一旦创建,我们就会请求在内部的配置文件信息在里面方法:

class profileviewmodel(
userId: String,
存储库:profilerepository,
backgroundScheduler: Scheduler = Schedulers.io(),
mainScheduler: Scheduler = AndroidSchedulers.mainThread()
):ViewModel(){
private val _state = MutableLiveData()
val状态:liveata = _state

在里面 {
_state.value = profileviewstate.loading()

存储库
.fetchprofile(userid)
.subscrifton(backgroundscheduler)
.observeon(主管机构)
。subscribe(
{ user ->
_state.value = ProfileViewState.success(user)
},
{ error ->
_state.value = ProfileViewState.error(error)
}
的)
}
}

As soon as our ViewModel is created, we’ll emit a loading state to our LiveData. Then, we’ll request a profile, and post a new ProfileViewState if the call succeeds or fails.

这是我们在测试的组件所需的一切。接下来我们可以测试它。

测试ViewModel.

我们将从一个正案例测试开始以确保当我们请求用户时,将发出加载状态,然后是数据状态。让我们看看测试看起来像什么:

class profileviewmodeltest {

@测试
fun loadProfile() {
val testuser =用户(userid =“123”)
val mockrepository = mockito.mock(profilerepository :: class.java)

whenever(mockRepository.fetchProfile(anyString()))
。thenReturn(Single.just(testUser))

val viewModel = ProfileViewModel(
userId = "123",
存储库= mockRepository,
backgroundScheduler = Schedulers.trampoline(),
mainScheduler = Schedulers.trampoline()
的)

val观察到= ViewModel.State.Testobserver()。观察值
assertThat(observedStates.size).isEqualTo(2)

val firststate =观察器[0]
val secondstate =观察器[1]
asserthat(firststate.loading).istrue()
assertthat(secondstate.data).isequalto(testuser)
}
}

If you’d like to see the implementation of.testobserver()你可以找到它这个小麦

The Test Fails

令我们惊讶的是,这个测试将失败!我们正在嘲笑成功的数据请求,所以我们应该期待我们的观察到有两个条目:一个用于加载状态,一个用于成功的数据状态。运行测试后,第一个断言失败。我们的测试说观察到的is one, and the value is the data state.

In other words, our test was not able to verify that a loading state occurred.

发生了什么?

让我们考虑我们的单位测试中的不同比实际代码不同。在我们的单元测试中,我们通过了scheduler.trampoline()from RxJava which helps to make the network request run as if it were sequential. In terms of this test, it's as if the network request succeeds instantly once the ViewModel is created.

Then, after our ViewModel is created, we apply a test observer on theViewModel.stateLivevata,已经处于加载数据状态。这意味着加载状态发生在时间太远了 - 我们无法在创建之前观察一个LiveData,因此我们无法验证有史以来发生的加载状态。

这种并发症是由我们的嘲弄库引起的,它告诉我们的模拟存储库立即返回信息。相反,我们可以创建我们自己的虚假实现,我们可以完全控制,并可以控制数据的排放,以确保我们的单元测试捕获加载状态。

创造假.

创建一个假的实现中,我们开始创造ing a new class that implements our interface. Remember, we don’t want our fake to return data right away, because that will just cause the same problem. Instead, since we’re using RxJava, we can implement our fake in a way that uses aBehaviorSubject我们可以控制的幕后。

class fakerepository:profilerepository {
Private Val Usersubject:行为原则 = spateIorsubject.create()

覆盖有趣的fetchprofile(userid:string):单个 {
return usersubject.hide()。FirstorError()
}
}

如果您使用的是Coroutines,则在此处使用此处可能会更改,但概念保持不变:我们不想从中返回fetchprofile()立即提供信息。我们希望确保我们的伪造实现准确地在发出数据时。

Controlling Data Emissions

Since our fake implementation is using aBehaviorSubject作为底层数据源,我们可以创建我们自己的公共方法,每当我们喜欢时才发出:

class fakerepository:profilerepository {
// ...

有趣的发出者(用户:用户){
this.usersubject.onnext(用户)
}
}

更新测试以验证加载状态

现在我们在从我们的存储库发出数据时我们有一个系统的系统,我们可以利用这一点来准确地测试我们的加载状态。我们将遵循这个配方:

  1. Create our fake repository and ViewModel component
  2. 由于我们的假期不立即发出数据,我们将能够验证我们是否处于加载状态。
  3. 我们可以控制伪造实现以发出数据。
  4. 最后,我们可以验证我们的ViewModel现在处于加载的数据状态。
class profileviewmodeltest {

@测试
fun loadProfile() {
val testuser =用户(userid =“123”)

// Using a fake instead of mockito
val fakerepository = fakerepository()

// Create our ViewModel
val viewModel = ProfileViewModel(
userId = "123",
存储库= fakeRepository,
backgroundScheduler = Schedulers.trampoline(),
mainScheduler = Schedulers.trampoline()
的)

val testobserver = ViewModel.State.TestoBserver()
val observedStates = testObserver.observedValues

//验证我们只有加载状态
assertthat(观察到的静音).isequalto(1)
val firststate =观察器[0]
asserthat(firststate.loading).istrue()

//发出数据,验证我们移动到一个
// loaded data state
fakerepository.emituser(testuser)
assertThat(observedStates.size).isEqualTo(2)
val secondstate =观察器[1]
assertthat(secondstate.data).isequalto(testuser)
}
}

搭档

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

我希望你找到这个有用的!如果您有其他使用假与模拟的例子,请告诉我Twitter

Interested in working for OkCupid?We’re hiring

最初出版https://tech.188bet金宝搏官网okcupid.com.2020年6月12日。

188bet金宝搏官网Okcupid Tech Blog.

Read stories from the OkCupid engineering team connecting millions of people every day

188bet金宝搏官网Okcupid Tech Blog.

188bet金宝搏官网Okcupid的工程团队负责每天匹配数百万人。阅读Okcupid Tech Blog上的故事188bet金宝搏官网

188bet金宝搏官网Okcupid Tech Blog.

188bet金宝搏官网Okcupid的工程团队负责每天匹配数百万人。阅读Okcupid Tech Blog上的故事188bet金宝搏官网