iOS单元测试中如何验证异步函数未被调用?
验证异步函数未被调用的XCTest方案
要验证异步函数未被调用,核心思路是通过Mock对象跟踪每个函数的调用次数,配合XCTestExpectation确保目标异步操作完成后,再断言其余函数的调用计数为0。针对你的Launcher场景,具体实现步骤如下:
1. 抽象协议(解耦测试与实现)
先给Launcher定义协议,这样可以用Mock类替代真实实现,方便跟踪调用:
protocol LauncherProtocol { func functionA(param: String, completion: @escaping () -> Void) func functionB(param: String, completion: @escaping () -> Void) func functionC(param: String, completion: @escaping () -> Void) func functionD(param: String, completion: @escaping () -> Void) func launch(with param: String) }
2. 实现Mock类(记录调用次数)
创建Mock类实现上述协议,给每个函数添加调用计数器,同时在目标函数中触发测试预期:
class LauncherMock: LauncherProtocol { // 记录每个函数的调用次数 var funcACalledCount = 0 var funcBCalledCount = 0 var funcCCalledCount = 0 var funcDCalledCount = 0 // 关联测试预期,用于等待目标函数完成 var targetExpectation: XCTestExpectation? func functionA(param: String, completion: @escaping () -> Void) { funcACalledCount += 1 // 模拟异步执行,完成后触发预期 DispatchQueue.global().async { completion() self.targetExpectation?.fulfill() } } func functionB(param: String, completion: @escaping () -> Void) { funcBCalledCount += 1 DispatchQueue.global().async { completion() } } func functionC(param: String, completion: @escaping () -> Void) { funcCCalledCount += 1 DispatchQueue.global().async { completion() } } func functionD(param: String, completion: @escaping () -> Void) { funcDCalledCount += 1 DispatchQueue.global().async { completion() } } // 模拟真实Launcher的触发逻辑:参数为"specific"时仅调用functionA func launch(with param: String) { if param == "specific" { functionA(param: param, completion: {}) } // 其他参数逻辑省略 } }
3. 编写单元测试
在测试用例中,初始化Mock对象、设置预期、触发逻辑后,等待目标函数完成,最后断言计数:
class LauncherTests: XCTestCase { func testLaunchWithSpecificParamCallsOnlyFunctionA() { let mockLauncher = LauncherMock() // 创建针对目标函数的测试预期 let expectation = self.expectation(description: "FunctionA should be invoked") mockLauncher.targetExpectation = expectation // 触发Launcher的启动逻辑 mockLauncher.launch(with: "specific") // 等待预期完成,超时时间设为足够覆盖异步操作的时长 waitForExpectations(timeout: 2) { error in if let error = error { XCTFail("Wait failed: \(error.localizedDescription)") } } // 验证目标函数被调用1次,其余函数未被调用(计数为0) XCTAssertEqual(mockLauncher.funcACalledCount, 1) XCTAssertEqual(mockLauncher.funcBCalledCount, 0) XCTAssertEqual(mockLauncher.funcCCalledCount, 0) XCTAssertEqual(mockLauncher.funcDCalledCount, 0) } }
关键注意事项
- 超时时间设置:要保证超时时间足够长,能覆盖所有可能的异步操作执行时长,避免因异步未完成导致误判。
- Mock的异步模拟:尽量贴近真实实现的异步逻辑(比如用
DispatchQueue),避免测试与实际行为脱节。 - 协议解耦:使用协议而非直接依赖真实Launcher类,让测试更灵活,也符合依赖倒置原则。
内容的提问来源于stack exchange,提问作者Lucy Gu
相关产品推荐
相关产品推荐

