How to test completion handlers with Swift Testing and XCTest 如何使用 Swift Testing 和 XCTest 测试补全处理程序

How to test completion handlers with Swift Testing and XCTest 如何使用 Swift Testing 和 XCTest 测试补全处理程序

温馨提示:本文最后更新于2025-02-10 10:34:12,某些文章具有时效性,若有错误或已失效,请在下方留言

When you’re dealing with older concurrency code that relies on callback functions that get run when some work completes rather than using Swift concurrency’s async/await approach, it’s important your test code waits fully for the completion handler to be called, then makes any assertions against the result of that completion handler.
当您处理依赖于在某些工作完成时运行的回调函数的旧并发代码,而不是使用 Swift 并发的 async/await 方法时,您的测试代码必须完全等待完成处理程序被调用,然后针对该完成处理程序的结果进行任何断言。

As an example, we might have a class like this one:
例如,我们可能有一个这样的类:

class ViewModel {
    func loadReadings(completion: @escaping ([Double]) -> Void) {
        let url = URL(string: "https://hws.dev/readings.json")!

        URLSession.shared.dataTask(with: url) { data, response, error in
            if let data {
                if let numbers = try? JSONDecoder().decode([Double].self, from: data) {
                    completion(numbers)
                    return
                }
            }

            completion([])
        }.resume()
    }
}

That has a loadReadings() method that will fetch, decode, and return some data through a completion handler, although in practice your tests will likely add some kind of mock data here to avoid networking during test runs.
它有一个 loadReadings() 方法,该方法将通过完成处理程序获取、解码和返回一些数据,尽管在实践中,你的测试可能会在此处添加某种模拟数据,以避免在测试运行期间联网。

Testing this correctly depends on which testing framework you’re using. With Swift Testing, you should use a continuation that resumes when the completion handler is called, like this:
正确测试此测试取决于您使用的测试框架。使用 Swift Testing,您应该使用在调用完成处理程序时恢复的 continuation,如下所示:

@Test("Loading view model readings")
func loadReadings() async {
    let viewModel = ViewModel()

    await withCheckedContinuation { continuation in
        viewModel.loadReadings { readings in
            #expect(readings.count >= 10, "At least 10 readings must be returned.")
            continuation.resume()
        }
    }
}

With XCTest, you should use XCTestExpectation instead, like this:
对于 XCTest,您应该改用 XCTestExpectation,如下所示:

final class DataHandlingTests: XCTestCase {
    func test_loadViewModelReadings() async {
        let viewModel = ViewModel()
        let expectation = XCTestExpectation(description: "Check view model readings")

        viewModel.loadReadings { readings in
            if readings.count >= 10 {
                expectation.fulfill()
            }
        }

        await fulfillment(of: [expectation], timeout: 10)
    }
}

Either way, the important things are:
无论哪种方式,重要的是:

  1. The assertions are made inside the completion closure.
    断言是在 completion 闭包进行的。
  2. The test itself is instructed to wait for the assertion to be made.
    指示测试本身等待断言。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享