单元测试RxSwift应用程序

我今天要谈论的主题是RxSwift应用程序的单元测试。 这是我的系列文章“如何在MVVM中使用RxSwift”的最后一部分,在该系列中,我们通过实现Friends应用程序学习了如何使用RxSwift。 剩下的唯一事情就是对应用程序进行单元测试。 RxSwift应用程序的单元测试与普通swift应用程序的单元测试非常相似。 再一次,最大的变化是我们与观察者一起处理所有的回调和数据更新。 在这篇文章中,我们将看到如何:

  1. 处理可观察对象并订阅事件。
  2. 模拟网络层以进行单元测试。
  3. 在单元测试中处理数据验证。

我们将测试的所有代码都在视图模型内部,因此您还将学习如何对视图模型进行单元测试。 如果您对单元测试的概念不太熟悉,建议您阅读我以前有关单元测试视图模型的文章。 您将获得单元测试的所有基本信息,并友好地提醒您为什么应该始终对应用程序进行单元测试! 😄

我们将通过对称为Friends的应用程序进行单元测试来学习这些知识。 Friends应用程序是我使用MVVM模式实现的应用程序。 使用“朋友”应用,您可以下载朋友列表,并使用UITableView将其显示给用户。 您还可以使用该应用创建,更新和删除朋友。 这是一个简单的应用程序,具有足够的功能,可以处理开发iPhone应用程序时遇到的许多基本问题。 我首先编写了不带RxSwift的Friends应用程序,然后想看看如果我使用RxSwift会更改多少代码。 如果您想进一步了解纯MVVM应用程序的实现,请查看我有关Swift应用程序的MVVM模式的文章。 如果您想了解我们今天测试的应用程序是如何实现的,请查看这篇文章:如何在MVVM中使用RxSwift。

所有代码都可以从GitHub下载。 只要记得签出RxSwift分支即可。 但是现在,让我们开始吧!

我们将从检查如何对FriendsTableViewViewModel进行单元测试FriendsTableViewViewModel 。 这是处理向用户显示朋友列表的类。

由于FriendsTableViewViewModel发出网络请求,因此我们要做的第一件事是模拟网络层以进行测试。 通常,我们不希望单元测试发出网络请求,因为:

  1. 从服务器获取答案可能需要一些时间,这会使测试运行缓慢。
  2. 由于网络或服务器,测试可能会失败,这使得无法可靠地验证结果。

通过模拟网络层,我们可以为当前测试用例返回合适的答案。

AppServerClient是处理应用程序中所有网络的类。 如果打开该类,我们将看到它具有一个名为getFriends的函数。 这是我们要在第一个测试中覆盖的功能。 它下载朋友列表,然后我们向用户显示该列表。 函数定义如下所示:

我们可以定义一个协议,该协议具有与AppServerClient相同的所有功能定义。 然后,我们可以使AppServerClient和模拟实现都符合该协议。 但是由于我们还没有可用的协议,因此我们将使用良好的旧继承。

MockAppServerClient继承自AppServerClient而我们已经覆盖了getFriendsFunction 。 我们在函数内部所做的第一件事是,我们创建了一个从函数返回的Observable 。 我们为create函数传递一个块,并将Switch用于名为getFriendsResult的变量。

getFriendsResult是用于定义网络请求的不同结果的变量。 成功的情况下,它包含一个朋友列表;失败的情况下,它包含一个错误。 稍后,我们将检查如何为测试定义值。 在switch语句中,我们定义了.success.failure情况, .onNext订阅者发出带有朋友列表的.onError或带有错误值的.onError 。 我们已将getFriendsResult定义为Optional因为我们不想为模拟类定义初始化程序。 这就是为什么我们还需要在函数中定义.none大小写的原因。

最后,我们将Observable.create需要的虚拟一次性对象作为返回值返回。 RxSwift应用程序单元测试的第一步已经完成! 接下来,让我们编写第一个测试。 我们将使用依赖注入来传递刚刚创建的模拟网络层,并设置getFriendsResult以匹配我们的测试用例断言。

单元测试RxSwift — FriendsTableViewModel

在测试好友列表请求时,我们还希望在请求失败时检查视图是否处于正确状态。 这就是为什么我们在这里编写两个不同的测试的原因。 请求成功且失败的情况。 实际上,还有第三种状态。 服务器返回一个空的朋友列表,这意味着我们的用户没有朋友。 只是在开玩笑,这意味着用户尚未上传任何朋友信息。 这种情况类似于获取实际上包含朋友信息的列表,因此我将其留给您从代码中找出来。

但是首先,让我们看一下成功的测试。

由于我们正在使用RxSwift,因此我们首先需要创建一个DisposeBag 。 接下来,我们将创建MockAppServerClient 。 创建后,我们立即将getFriendResult定义为成功,并将有效负载设置为虚拟朋友对象。 Friend.with()是仅为测试目标定义的静态函数,可帮助我们创建虚拟朋友:

接下来,我们将创建我们要测试的视图模型。 创建它时,我们将提供模拟网络客户端作为参数。 这种技术称为依赖注入,它可以帮助我们使类可测试。 在我们的视图模型初始化程序中,我们定义了如下参数:

因此,如果不将网络客户端作为参数,则使用默认版本(实际上是发出网络请求)。

抱歉,有些解释cluster,但让我们继续测试功能代码。 接下来,我们将调用viewModel.getFriends()并确保单元格已准备就绪。 现在我们已经完成了所有设置,我们需要在下载好友列表之后以某种方式确认我们的视图模型处于正确状态。 这就是XCTest框架XCTest作用的地方。 我们将使用expectation来创建一个名为expectNormalCellCreated的变量。 期望的工作方式是,它们需要在一定时间(我们在测试用例中定义)之前得到满足,或者测试被标记为失败。 在这里,我们期望在提供的输入数据中,即, friendCellsPublishSubject包含的数组中的第一项确实是普通单元格。

通过订阅PublishSubject并检查onNext函数内部的第一项是普通单元格,我们将确保是这种情况。 如果该项目通过了该检查,则我们将期望变量称为fulfill 。 我们使用XCTAssertTrue检查内容,以便在输入错误的情况下测试立即失败。 如果我们的应用程序进行了大量测试,则可以减少运行它们的时间。 这样,我们就不会一直等待失败的测试用例。 在subscription subscribe()调用之后,我们要做的最后一件事是将返回的对象添加到disposeBag中。

菲尤夫,终于我们准备就绪。 现在,我们只需要定义一个时限即可满足期望。 同样,我们将使用XCTest框架中的函数。 wait将一系列期望作为参数(所有这些都需要实现)以及一个需要满足这些条件的时间限制。 由于模拟类中的getFriends调用不会发出任何网络请求,因为它实际上是同步的,因此我们可以将超时定义为0.1秒。

现在,当我们按下运行按钮时,我们将看到测试通过。 接下来,让我们定义失败的情况!

单元测试RxSwift-失败的好友请求

当发生错误时,我们在朋友视图中的表视图显示一个错误单元格。 这次,我们要测试friendCells包含错误单元格:

测试用例与我们刚经历的非常相似。 但是这次, getFriendsResult被定义为失败。 另外,现在,如果有case语句,则检查数组内是否确实存在.error单元。 其余代码与我们刚刚经历的情况相同,因此您可以自己弄清楚。

FriendsTableViewViewModelTests类中还有很多测试,但是所有测试都遵循这两种情况中定义的相同模式。 检查他们,如果您有任何问题,疑问或意见,只需在Twitter上发给我DM或在下面发表评论,我会尽快回复您:)。

现在,让我们检查一下在对RxSwift应用程序进行单元测试时如何验证用户输入数据。

FriendViewModel是负责添加和编辑朋友的类型。 FriendViewController是绘制UI的视图。 根据我们在做什么,我们将在打开视图时传递AddFriendViewModelUpdateFriendViewModel 。 它们都符合FriendViewModel协议。 今天,我们将研究AddFriendViewModel ,它用于向服务器添加朋友信息,并了解如何为它编写测试。

测试AddFriendViewModel

要发送朋友信息,必须填写所有字段(名字,姓氏和电话号码)。 首先,我们将检查字段验证是否有效。 除非用户提供了有效数据,否则我们将通过禁用提交按钮来防止用户发送无效信息。 我们可以通过订阅名为Observable submitButtonEnabled 。 每当数据更改时,它都会发出一个事件,我们可以订阅它以检查状态。 在AddFriendViewModelTests内部,我们有一个名为validateInputSuccess的测试,可以完成所有这些工作。

该代码看起来非常熟悉。 我们创建disposeBagmockFriendmockAppServerClient变量。 然后,我们将创建一个viewModel并使用依赖注入,以便使用我们的模拟网络客户端。 为AddFriendViewModelTests定义的模拟网络客户端与我们刚刚经历的模拟客户端非常相似,您可以直接从代码中找出它。

接下来,我们将为用户输入设置数据:名字,姓氏和电话号码。 之后,我们再次创建一个expectation ,这次是更改Submit按钮状态。 然后,我们订阅该事件,并仅在发出的状态为true后才满足期望。 在测试的最后一行中,我们使用wait并等待期望的实现。 就是这样! 现在我们已经测试了数据验证是否有效。

现在,让我们检查一下我们将要通过的最后一个测试。 添加新朋友的成功案例。

单元测试RxSwift-测试成功的朋友创建

现在,这是我们熟悉的练习。 DisposeBag,模拟网络,创建视图模型,设置输入数据和设置期望。 成功创建朋友后,我们将导航回到朋友列表视图。 我们知道导航是在发出onNavigateBack之后完成的。 因此,要验证我们的数据已传递到服务器,我们只需确保仅在调用onNavigateBack之后才满足期望。 之后,我们将调用onNextsubmitButtonTapped以开始将信息发送到服务器。 通常,这是由在UI中按下“提交”按钮的用户完成的。 如果服务器响应是我们期望的(因为我们只是将其定义为我们想要的),那么最终将发出onNavigateBack 。 现在,当我们运行测试时,我们将看到它通过。

您还可以在AddFriendViewModelTests类中找到testAddFriendFailure案例。 因为我们已经拥有实施测试所需的所有信息,所以我们将不做这些。 如果您不了解某些内容,请询问,我会向您解释。

这就是我今天要经历的一切! 我们了解了在项目中使用RxSwift时如何测试视图模型。 我们还学习了如何模拟网络层并在测试中使用依赖注入。 在您经历了一次之后,所有这些都非常简单,但是第一次可能会有些困难。 希望我能为您提供帮助,并希望再次在这里见到您! 现在,感谢您的阅读,祝我的朋友愉快!

-Jussi Suojanen

Swifty Sw开发人员


查看更多:

英文:www.vincit.com

芬兰语:www.vincit.fi