【问题标题】:SwiftUI Combine - How to test waiting for a publisher's async resultSwiftUI Combine - 如何测试等待发布者的异步结果
【发布时间】:2022-07-28 01:46:21
【问题描述】:

我正在监听发布者的更改,然后在我的管道中异步获取一些数据并使用结果更新视图。但是,我不确定如何使它可测试。我怎样才能最好地等到期望得到满足?

查看

struct ContentView: View {
    @StateObject var viewModel = ContentViewModel()

    var body: some View {
        NavigationView {
            List(viewModel.results, id: \.self) {
                Text($0)
            }
            .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 这适用于我的单元和快照测试,但我担心:

  1. 如果不被外部调用,暴露是不好的做法?
  2. 我没有测试我的发布者管道
func testExample_Workaround() async {
    // Given
    let viewModel = ContentViewModel()

    // When
    await viewModel.fetchResults()

    // Then
    XCTAssertEqual(viewModel.results, ["01", "02", "03"])
}

【问题讨论】:

    标签: swiftui combine swift-concurrency


    【解决方案1】:

    需要通过expectation异步等待,通过publisher查看结果。

    这是可能的方法。使用 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($0, ["01", "02", "03"])
                    expect.fulfill()
                }
                .store(in: &cancelables)
    
            viewModel.searchText = "123"
            wait(for: [expect], timeout: 3)
        }
    

    backup

    【讨论】:

    • 这种方式的缺点是,如果期望没有实现(如应用程序端的错误),您必须等到超时(此处为 3 秒)运行才能完成测试。如果你有 100 个这样的测试用例,你会等待 5 分钟...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-11
    • 1970-01-01
    • 1970-01-01
    • 2021-10-22
    • 2016-12-05
    • 2018-08-28
    • 1970-01-01
    相关资源
    最近更新 更多