Combine 系列
- Swift Combine 从入门到精通一
- Swift Combine 发布者订阅者操作者 从入门到精通二
- Swift Combine 管道 从入门到精通三
- Swift Combine 发布者publisher的生命周期 从入门到精通四
- Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
- Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
- Swift 使用 Combine 进行开发 从入门到精通七
- Swift 使用 Combine 管道和线程进行开发 从入门到精通八
- Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
- Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
- Swift Combine 用 Future 来封装异步请求 从入门到精通十一
- Swift Combine 有序的异步操作 从入门到精通十二
- Swift Combine 使用 flatMap 和 catch错误处理 从入门到精通十三
- Swift Combine 网络受限时从备用 URL 请求数据 从入门到精通十四
- Swift Combine 通过用户输入更新声明式 UI 从入门到精通十五
- Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六
- Swift Combine 合并多个管道以更新 UI 元素 从入门到精通十七
- Swift Combine 通过包装基于 delegate 的 API 创建重复发布者 从入门到精通十八
- Swift Combine 响应NotificationCenter 的更新 从入门到精通十九
- Swift Combine 使用 ObservableObject 与 SwiftUI 模型作为发布源 从入门到精通二十
1. 测试和调试
Combine 中的发布者和订阅者接口是非常易于测试的。
借助 Combine 的可组合性,你可以利用此优势创建或消费符合 Publisher 协议的 API。
以 publisher protocol 为关键接口,你可以替换任何一方以单独验证你的代码。
例如,如果你的代码专注于通过 Combine 从外部 Web 服务中提供其数据,则可能会使此接口遵循 AnyPublisher<Data, Error>
。 然后,你可以使用该接口独立测试管道的任何一侧。
-
你可以模拟 API 请求和可能响应的数据,包括各种错误条件。 这可以包括使用
Just
或Fail
创建的发布者来返回数据,或者更复杂的使用Future
。 使用这些方案都不需要你进行实际的网络接口调用。 -
同样,你也可以隔离测试,让发布者进行 API 调用,并验证预期的各种成功和失败条件。
2. 使用 XCTestExpectation 测试发布者
目的:用于测试发布者(以及连接的任何管道)
当你测试发布者或创建发布者的某些代码时,你可能无法控制发布者何时返回数据以进行测试。 由其订阅者驱动的 Combine 可以设置一个同步事件来启动数据流。 你可以使用 XCTestExpectation
等待一段确定的时间之后,再调用 completion 闭包进行测试。
此与 Combine 一起使用的模式:
- 在测试中设置 expectation。
- 确定要测试的代码。
- 设置要调用的代码,以便在执行成功的情况下,你调用 expectation 的
.fulfill()
函数。 - 设置具有明确超时时间的
wait()
函数,如果 expectation 在该时间窗口内未调用fulfill()
,则测试将失败。
如果你正在测试管道中的结果数据,那么在 sink
操作符的 receiveValue
闭包中触发 fulfill()
函数是非常方便的。 如果你正在测试管道中的失败情况,则通常在 sink
操作符的 receiveCompletion
闭包中包含 fulfill()
方法是有效的。
下列示例显示使用 expectation 测试一次性发布者(本例中是 URLSession.dataTaskPublisher
),并期望数据在不出错的情况下流动。
UsingCombineTests/DataTaskPublisherTests.swift - testDataTaskPublisher
func testDataTaskPublisher() {
// setup
let expectation = XCTestExpectation(description: "Download from \(String(describing: testURL))") // 1
let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: self.testURL!)
// validate
.sink(receiveCompletion: { fini in
print(".sink() received the completion", String(describing: fini))
switch fini {
case .finished: expectation.fulfill() // 2
case .failure: XCTFail() // 3
}
}, receiveValue: { (data, response) in
guard let httpResponse = response as? HTTPURLResponse else {
XCTFail("Unable to parse response an HTTPURLResponse")
return
}
XCTAssertNotNil(data)
// print(".sink() data received \(data)")
XCTAssertNotNil(httpResponse)
XCTAssertEqual(httpResponse.statusCode, 200) // 4
// print(".sink() httpResponse received \(httpResponse)")
})
XCTAssertNotNil(remoteDataPublisher)
wait(for: [expectation], timeout: 5.0) // 5
}
Expectation
设置为一个字符串,这样在发生失败时更容易调试。 此字符串仅在测试失败时才能看到。 我们在这里测试的代码是dataTaskPublisher
从测试前就已定义好的预设的 URL 中取回数据。 发布者通过将 sink 订阅者连接到它开始触发请求。 如果没有expectation
,代码仍将运行,但构建的测试运行结构将不会等到结果返回之后再去检查是否有任何意外。 测试中的expectation
“暂停测试” 去等待响应,让操作符先发挥它们的作用。- 在这个例子中,测试期望可以成功完成并正常终止,因此在
receiveCompletion
闭包内调用expectation.fulfill()
,具体是接收到.finished
completion
后调用。 - 由于我们不期望失败,如果我们收到
.failure
completion
,我们也明确地调用XCTFail()
。 - 我们在
receiveValue
中还有一些其他断言。 由于此发布者设置返回单个值然后终止,因此我们可以对收到的数据进行内联断言。 如果我们收到多个值,那么我们可以收集这些值,并就事后收到的内容做出断言。 - 此测试使用单个
expectation
,但你可以包含多个独立的expectation
,去要求它们都被fulfill()
。 它还规定此测试的最长运行时间为 5 秒。 测试并不总是需要五秒钟,因为一旦收到在这里插入代码片
fulfill,它就会完成。 如果出于某种原因,测试需要超过五秒钟的响应时间,XCTest 将报告测试失败。
参考
https://heckj.github.io/swiftui-notes/index_zh-CN.html
代码
https://github.com/heckj/swiftui-notes