You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

协议类型属性无法赋值符合协议对象及单元测试伪认证器问题

解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:12:52