我有一个viewMode,它决定iCloud是启用还是禁用,结果是提示用户是否登录iCloud。
有没有办法从XCTest向iCloud进行渐进式登录/注销,以可靠地测试所有路径?
这是我的测试
func testShowLoginButtonForiCloud() {
let viewModel = OnboardingViewModel()
let expectation = XCTestExpectation(description: "Wait for CKContainer auth check")
var iCloudEnabled: Bool?
viewModel.shouldShowiCloudLogin { result, error in
iCloudEnabled = result
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
XCTAssertNotNil(iCloudEnabled)
XCTAssertFalse(iCloudEnabled!)
}
这是我的视图模型
typealias Completion = (Bool, Error?) -> Void
final class OnboardingViewModel {
func shouldShowiCloudLogin(completion: @escaping Completion) {
CKContainer.default().accountStatus { (status, error) in
switch status {
case .available :
completion(true, nil)
default :
completion(false, error)
}
}
}
}
最佳答案
我们能以编程方式登录到CloudKit进行单元测试吗?这是不可取的,因为即使我们可以这样做,测试也将是缓慢而脆弱的。相反,将CloudKit视为架构边界。单元测试可以一直到这个边界。我们可以假装有东西从边界回来。这样,我们可以测试所有路径。
要将此边界编程到代码中,请使用协议。此协议将是一个切片,其中仅包含所需的CKContainer方法。(这是操作中的接口分离原则。)由于CKContainer已经实现了此方法,我们可以将其作为空扩展附加。
protocol CKContainerProtocol {
func accountStatus(completionHandler: @escaping (CKAccountStatus, Error?) -> Void)
}
extension CKContainer: CKContainerProtocol {}
然后将属性添加到视图模型:
var cloudKitContainer: CKContainerProtocol = CKContainer.default()
默认值意味着您的代码将继续使用真实的CKContainer,除非另有说明。更改代码以调用
cloudKitContainer
而不是CKContainer.default()
。然后在测试代码中,您可以提供CKContainerProtocol的不同实现。这会让你做劈头和嘲笑。您可以确认只调用一次
accountStatus()
。您可以使用不同的CKAccountStatus值来执行它的闭包,以确认如何调用完成闭包。(我在iOS Unit Testing by Example: XCTest Tips and Techniques Using Swift中更详细地介绍了类似的内容)