协议类型属性无法赋值符合协议对象及单元测试伪认证器问题
解决Fake Authenticator的实现与协议类型赋值问题
首先,先帮你实现一个能手动控制登录状态的FakeAuthenticator,完全贴合你现有的Authenticator协议,专门用于单元测试:
import Foundation // 自定义错误类型,方便测试时区分状态 enum FakeAuthError: Error { case userNotLoggedIn } class FakeAuthenticator: Authenticator { // 手动开关登录状态 var isLoggedIn: Bool = false // 可自定义的模拟token var mockAccessToken: String? = "unit-test-fake-token-123" func getValidAccessToken(completionHandler: @escaping (Error?, String?) -> Void) { if isLoggedIn, let token = mockAccessToken { // 登录状态下返回模拟token completionHandler(nil, token) } else { // 未登录时返回自定义错误 completionHandler(FakeAuthError.userNotLoggedIn, nil) } } }
接下来解决你遇到的Cannot set value conforming to protocol to property with protocol type报错,这个问题通常有两种常见原因,对应不同的解决方式:
1. 属性声明为不可变的let
如果你的业务代码里,持有Authenticator的属性是用let声明的,那肯定无法重新赋值为FakeAuthenticator实例——因为let是不可变常量。只需要把它改成var即可:
错误示例:
class UserDataManager { // let声明的属性无法修改 let authenticator: Authenticator init(authenticator: Authenticator) { self.authenticator = authenticator } }
修正后:
class UserDataManager { // 用var声明,允许替换为Fake实现 var authenticator: Authenticator init(authenticator: Authenticator) { self.authenticator = authenticator } }
2. 协议包含关联类型(后续扩展协议时可能遇到)
如果你的Authenticator协议后续添加了associatedtype(关联类型),直接用协议类型作为属性会出现类型不兼容的问题,这时需要通过类型擦除来包装协议实现:
// 类型擦除的包装类 class AnyAuthenticator: Authenticator { private let _getToken: (@escaping (Error?, String?) -> Void) -> Void init<Auth: Authenticator>(_ auth: Auth) { _getToken = auth.getValidAccessToken } func getValidAccessToken(completionHandler: @escaping (Error?, String?) -> Void) { _getToken(completionHandler) } }
然后业务代码的属性就可以声明为var authenticator: AnyAuthenticator,赋值时用包装类包裹:
// 生产环境用真实认证器 let realAuth = AnyAuthenticator(OIDCAuthenticator()) // 测试环境用Fake认证器 let fakeAuth = AnyAuthenticator(FakeAuthenticator())
单元测试使用示例
最后给你一个完整的单元测试示例,展示如何用FakeAuthenticator控制登录状态:
import XCTest @testable import YourAppName class UserDataManagerTests: XCTestCase { func testFetchUserWhenLoggedIn() { // 初始化Fake认证器并设置为登录状态 let fakeAuth = FakeAuthenticator() fakeAuth.isLoggedIn = true let manager = UserDataManager(authenticator: fakeAuth) let expectation = self.expectation(description: "Fetch user data") manager.fetchUserData { error, data in XCTAssertNil(error) XCTAssertEqual(data?["token"] as? String, fakeAuth.mockAccessToken) expectation.fulfill() } waitForExpectations(timeout: 2, handler: nil) } func testFetchUserWhenLoggedOut() { let fakeAuth = FakeAuthenticator() fakeAuth.isLoggedIn = false let manager = UserDataManager(authenticator: fakeAuth) let expectation = self.expectation(description: "Fail to fetch user data") manager.fetchUserData { error, data in XCTAssertEqual(error as? FakeAuthError, .userNotLoggedIn) XCTAssertNil(data) expectation.fulfill() } waitForExpectations(timeout: 2, handler: nil) } }
这样就能完全绕过真实的OIDC API,在单元测试中自由控制用户的登录状态啦。
内容的提问来源于stack exchange,提问作者Declan McKenna
相关产品推荐
相关产品推荐

