为HealthKit的requestAuthorization编写单元测试遇超时错误求助
问题
通过生成HKHealthStore的Mock对象编写requestAuthorization方法的单元测试时,出现异步等待超时错误:2秒内未满足预期「Successfully tested requestAuthorization by returning true.」。相关代码如下:
业务代码
func requestAuthorization(completion: @escaping(Bool?, HealthError?) -> Void) { self.healthStore?.requestAuthorization(toShare: self.allTypes as? Set<HKSampleType>, read: self.allTypes, completion: { authorized, error in if error != nil { print("ERROR: \(error)") completion(nil, .unableToAuthorizeAccess) } completion(authorized, nil) }) }
测试代码
func testRequestAuthorization_CanReturnTrue() { let expectation = expectation(description: "Successfully tested requestAuthorization by returning true.") sut?.requestAuthorization { authorized, error in if error != nil { print(error!) } guard let authorized = authorized else { return } XCTAssertTrue(authorized) expectation.fulfill() } wait(for: [expectation], timeout: 2) }
Mock对象重写方法
override func requestAuthorization(toShare typesToShare: Set<HKSampleType>?, read typesToRead: Set<HKObjectType>?, completion: @escaping (Bool, Error?) -> Void) { invokedRequestAuthorization = true invokedRequestAuthorizationCount += 1 invokedRequestAuthorizationParameters = (typesToShare, typesToRead) invokedRequestAuthorizationParametersList.append((typesToShare, typesToRead)) if let result = stubbedRequestAuthorizationCompletionResult { print("RESULT: \(result)") completion(result.0, result.1) } }
问题分析
超时的核心原因有两个:
- Mock的返回结果未设置:Mock的
requestAuthorization方法仅在stubbedRequestAuthorizationCompletionResult有值时才会调用completion闭包,若未赋值,闭包永远不会执行,导致测试预期无法被满足,最终超时。 - 业务代码存在重复调用completion的问题:当error存在时,业务代码先调用一次
completion(nil, .unableToAuthorizeAccess),之后又执行completion(authorized, nil),逻辑混乱可能影响测试闭包的正常执行。
解决方案
1. 给Mock对象设置返回结果
在测试方法中,提前给Mock的stubbedRequestAuthorizationCompletionResult赋值,确保它能触发completion闭包:
func testRequestAuthorization_CanReturnTrue() { let expectation = expectation(description: "Successfully tested requestAuthorization by returning true.") // 给Mock实例设置授权成功的返回结果 let mockHealthStore = yourMockHealthStoreInstance // 替换为你实际的Mock对象实例 mockHealthStore.stubbedRequestAuthorizationCompletionResult = (true, nil) sut?.healthStore = mockHealthStore sut?.requestAuthorization { authorized, error in if let error = error { print(error) } guard let authorized = authorized else { XCTFail("授权状态返回nil") expectation.fulfill() return } XCTAssertTrue(authorized) expectation.fulfill() } wait(for: [expectation], timeout: 2) }
2. 修复业务代码的completion重复调用
在业务代码中,当捕获到error时,调用completion后直接返回,避免后续重复执行:
func requestAuthorization(completion: @escaping(Bool?, HealthError?) -> Void) { self.healthStore?.requestAuthorization(toShare: self.allTypes as? Set<HKSampleType>, read: self.allTypes, completion: { authorized, error in if let error = error { print("ERROR: \(error)") completion(nil, .unableToAuthorizeAccess) return // 新增return,终止后续代码执行 } completion(authorized, nil) }) }
3. 测试中增加异常兜底处理
在测试的闭包内,如果authorized为nil,主动标记测试失败并完成预期,避免静默等待超时:
guard let authorized = authorized else { XCTFail("未获取到有效授权状态") expectation.fulfill() return }
内容的提问来源于stack exchange,提问作者Shak Feizi
相关产品推荐
相关产品推荐

