ios - 如何测试使用 DispatchQueue.main.async 调用的方法?
问题描述
在代码中我这样做:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateBadgeValuesForTabBarItems()
}
private func updateBadgeValuesForTabBarItems() {
DispatchQueue.main.async {
self.setBadge(value: self.viewModel.numberOfUnreadMessages, for: .threads)
self.setBadge(value: self.viewModel.numberOfActiveTasks, for: .tasks)
self.setBadge(value: self.viewModel.numberOfUnreadNotifications, for: .notifications)
}
}
并在测试中:
func testViewDidAppear() {
let view = TabBarView()
let model = MockTabBarViewModel()
let center = NotificationCenter()
let controller = TabBarController(view: view, viewModel: model, notificationCenter: center)
controller.viewDidLoad()
XCTAssertFalse(model.numberOfActiveTasksWasCalled)
XCTAssertFalse(model.numberOfUnreadMessagesWasCalled)
XCTAssertFalse(model.numberOfUnreadNotificationsWasCalled)
XCTAssertFalse(model.indexForTypeWasCalled)
controller.viewDidAppear(false)
XCTAssertTrue(model.numberOfActiveTasksWasCalled) //failed
XCTAssertTrue(model.numberOfUnreadMessagesWasCalled) //failed
XCTAssertTrue(model.numberOfUnreadNotificationsWasCalled) //failed
XCTAssertTrue(model.indexForTypeWasCalled) //failed
}
但是我最近的四个断言都失败了。为什么?我怎样才能成功地测试它?
解决方案
我认为测试这个的最好方法是模拟DispatchQueue
. 您可以创建一个协议来定义您要使用的功能:
protocol DispatchQueueType {
func async(execute work: @escaping @convention(block) () -> Void)
}
现在扩展DispatchQueue
以符合您的协议,例如:
extension DispatchQueue: DispatchQueueType {
func async(execute work: @escaping @convention(block) () -> Void) {
async(group: nil, qos: .unspecified, flags: [], execute: work)
}
}
请注意,我必须从协议中省略您在代码中未使用的参数,例如group
、qos
和flags
,因为协议不允许使用默认值。这就是扩展必须显式实现协议功能的原因。
现在,在您的测试中,创建一个DispatchQueue
符合该协议的模拟并同步调用闭包,例如:
final class DispatchQueueMock: DispatchQueueType {
func async(execute work: @escaping @convention(block) () -> Void) {
work()
}
}
现在,您需要做的就是相应地注入队列,可能在视图控制器中init
,例如:
final class ViewController: UIViewController {
let mainDispatchQueue: DispatchQueueType
init(mainDispatchQueue: DispatchQueueType = DispatchQueue.main) {
self.mainDispatchQueue = mainDispatchQueue
super.init(nibName: nil, bundle: nil)
}
func foo() {
mainDispatchQueue.async {
*perform asynchronous work*
}
}
}
最后,在您的测试中,您需要使用模拟调度队列创建视图控制器,例如:
func testFooSucceeds() {
let controller = ViewController(mainDispatchQueue: DispatchQueueMock())
controller.foo()
*assert work was performed successfully*
}
由于您在测试中使用了模拟队列,因此代码将同步执行,您无需沮丧地等待期望。
推荐阅读
- python - Python 3.9.1、Mac Big Sur、Numpy 和 Essentia
- python - Python,如何使用 lxml XPath?
- sql - 连接 3 个记录数不相等的表
- java - 为响应实体设置状态代码
- jmeter - 性能测试-如何将测试期间生成的身份验证令牌传递给下一个线程
- css - 使用 CSS 和 flex 布局将外部 div 宽度与内部 div 的最大宽度匹配
- c# - 如何管理硒中的下拉菜单?C#
- centos7 - 如果我们在 CentOS7 虚拟机中连接了多种磁盘类型怎么办?
- firebase - 在 BigQuery 中恢复过期的 Firebase 表并将数据保存时间超过 60 天
- python-3.x - 使用多个 if else 填充基于其他列值的列