SwiftUI Combine - 如何测试等待发布者的异步结果
SwiftUI Combine - How to test waiting for a publisher's async result
我正在侦听发布者的更改,然后在我的管道中异步获取一些数据并使用结果更新视图。但是,我不确定如何使它可测试。我怎样才能最好地等到达到预期?
查看
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
NavigationView {
List(viewModel.results, id: \.self) {
Text([=10=])
}
.searchable(text: $viewModel.searchText)
}
}
}
视图模型
final class ContentViewModel: ObservableObject {
@Published var searchText: String = ""
@Published var results: [String] = []
private var cancellables = Set<AnyCancellable>()
init() {
observeSearchText()
}
func observeSearchText() {
$searchText
.dropFirst()
.debounce(for: 0.8, scheduler: DispatchQueue.main)
.sink { _ in
Task {
await self.fetchResults()
}
}.store(in: &cancellables)
}
private func fetchResults() async {
do {
try await Task.sleep(nanoseconds: 1_000_000_000)
self.results = ["01", "02", "03"]
} catch {
//
}
}
}
测试
class ContentViewTests: XCTestCase {
func testExample() {
// Given
let viewModel = ContentViewModel()
// When
viewModel.searchText = "123"
// Then (FAILS - Not waiting properly for result/update)
XCTAssertEqual(viewModel.results, ["01", "02", "03"])
}
}
当前解决方法
如果我使 fetchResults()
可用,我可以 async/await 这适用于我的单元和快照测试,但我担心:
- 如果不被外部调用,暴露是不好的做法吗?
- 我没有测试我的发布管道
func testExample_Workaround() async {
// Given
let viewModel = ContentViewModel()
// When
await viewModel.fetchResults()
// Then
XCTAssertEqual(viewModel.results, ["01", "02", "03"])
}
您需要通过期望异步等待并通过发布者检查结果。
这是可能的方法。使用 Xcode 13.2 / iOS 15.2
测试
private var cancelables = Set<AnyCancellable>()
func testContentViewModel() {
// Given
let viewModel = ContentViewModel()
let expect = expectation(description: "results")
viewModel.$results
.dropFirst() // << skip initial value !!
.sink {
XCTAssertEqual([=10=], ["01", "02", "03"])
expect.fulfill()
}
.store(in: &cancelables)
viewModel.searchText = "123"
wait(for: [expect], timeout: 3)
}
我正在侦听发布者的更改,然后在我的管道中异步获取一些数据并使用结果更新视图。但是,我不确定如何使它可测试。我怎样才能最好地等到达到预期?
查看
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
NavigationView {
List(viewModel.results, id: \.self) {
Text([=10=])
}
.searchable(text: $viewModel.searchText)
}
}
}
视图模型
final class ContentViewModel: ObservableObject {
@Published var searchText: String = ""
@Published var results: [String] = []
private var cancellables = Set<AnyCancellable>()
init() {
observeSearchText()
}
func observeSearchText() {
$searchText
.dropFirst()
.debounce(for: 0.8, scheduler: DispatchQueue.main)
.sink { _ in
Task {
await self.fetchResults()
}
}.store(in: &cancellables)
}
private func fetchResults() async {
do {
try await Task.sleep(nanoseconds: 1_000_000_000)
self.results = ["01", "02", "03"]
} catch {
//
}
}
}
测试
class ContentViewTests: XCTestCase {
func testExample() {
// Given
let viewModel = ContentViewModel()
// When
viewModel.searchText = "123"
// Then (FAILS - Not waiting properly for result/update)
XCTAssertEqual(viewModel.results, ["01", "02", "03"])
}
}
当前解决方法
如果我使 fetchResults()
可用,我可以 async/await 这适用于我的单元和快照测试,但我担心:
- 如果不被外部调用,暴露是不好的做法吗?
- 我没有测试我的发布管道
func testExample_Workaround() async {
// Given
let viewModel = ContentViewModel()
// When
await viewModel.fetchResults()
// Then
XCTAssertEqual(viewModel.results, ["01", "02", "03"])
}
您需要通过期望异步等待并通过发布者检查结果。
这是可能的方法。使用 Xcode 13.2 / iOS 15.2
测试 private var cancelables = Set<AnyCancellable>()
func testContentViewModel() {
// Given
let viewModel = ContentViewModel()
let expect = expectation(description: "results")
viewModel.$results
.dropFirst() // << skip initial value !!
.sink {
XCTAssertEqual([=10=], ["01", "02", "03"])
expect.fulfill()
}
.store(in: &cancelables)
viewModel.searchText = "123"
wait(for: [expect], timeout: 3)
}