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

如何存根NEVPNManager的connection属性(NEVPNConnection)?如何用协议扩展NetworkExtension类做单元测试?

解决NEVPNManager协议扩展与Connection属性存根问题

你在为NetworkExtension类做协议抽象以支持单元测试时,遇到了两个核心问题:协议中connection属性无法适配NEVPNManager原生类型,以及如何存根该属性。我来一步步帮你解决:

第一步:修复协议扩展的兼容性问题

你遇到的connection属性协议定义失效,核心原因是类型不匹配:NEVPNManager的原生connection属性是NEVPNConnection类型,但你在协议里声明的是ConnectionProtocol,编译器无法自动完成类型适配。解决思路是先让NEVPNConnection遵守自定义协议,再显式实现协议中的属性。

1. 定义ConnectionProtocol并让NEVPNConnection遵守

先把你需要用到的NEVPNConnection属性、方法都加入ConnectionProtocol,再通过扩展让系统类自动遵守协议:

// 定义包含测试所需成员的ConnectionProtocol
protocol ConnectionProtocol {
    var status: NEVPNStatus { get }
    func startVPNTunnel(options: [String : String]?) throws
    func stopVPNTunnel()
    // 可添加其他NEVPNConnection的属性/方法
}

// 让系统的NEVPNConnection自动遵守协议
extension NEVPNConnection: ConnectionProtocol {}

2. 调整NEVPNManagerProtocol并显式实现connection属性

修改协议后,在NEVPNManager的扩展中显式返回原生connection(此时它已符合ConnectionProtocol):

protocol NEVPNManagerProtocol {
    var connection: ConnectionProtocol { get }
    func loadFromPreferences(completionHandler: @escaping (Error?) -> Void)
    func saveToPreferences(completionHandler: ((Error?) -> Void)?)
}

extension NEVPNManager: NEVPNManagerProtocol {
    // 显式实现协议中的connection属性,返回原生实例
    var connection: ConnectionProtocol {
        return self.connection
    }
}

这样编译器就不会再报错,因为我们明确指定了类型适配逻辑。

第二步:存根connection属性用于单元测试

现在可以创建Mock类模拟NEVPNManager和它的connection,完全控制测试中的行为:

1. 创建MockConnection(存根ConnectionProtocol)

这个类用来模拟NEVPNConnection的行为,可自由设置属性值、记录方法调用:

class MockConnection: ConnectionProtocol {
    // 可自定义的status属性,测试时灵活修改
    var status: NEVPNStatus = .disconnected
    
    // 记录方法调用状态,用于测试断言
    var startVPNTunnelCalled = false
    var stopVPNTunnelCalled = false
    
    func startVPNTunnel(options: [String : String]?) throws {
        startVPNTunnelCalled = true
        // 如需模拟错误,可在此抛出自定义错误
        // throw NSError(domain: "MockError", code: 0, userInfo: nil)
    }
    
    func stopVPNTunnel() {
        stopVPNTunnelCalled = true
    }
}

2. 创建MockNEVPNManager(存根NEVPNManagerProtocol)

这个类模拟NEVPNManager的行为,同时持有MockConnection实例:

class MockNEVPNManager: NEVPNManagerProtocol {
    // 自定义connection属性,传入预设的MockConnection
    var connection: ConnectionProtocol
    
    // 记录方法调用状态
    var loadFromPreferencesCalled = false
    var saveToPreferencesCalled = false
    
    init(connection: ConnectionProtocol) {
        self.connection = connection
    }
    
    func loadFromPreferences(completionHandler: @escaping (Error?) -> Void) {
        loadFromPreferencesCalled = true
        // 模拟加载完成的回调,可根据测试需求返回错误或nil
        completionHandler(nil)
    }
    
    func saveToPreferences(completionHandler: ((Error?) -> Void)?) {
        saveToPreferencesCalled = true
        completionHandler?(nil)
    }
}

3. 在单元测试中使用Mock类

现在可以自由控制Mock对象的行为,完成业务代码的单元测试:

func testVPNConnectionStart() {
    // 创建Mock实例
    let mockConnection = MockConnection()
    let mockManager = MockNEVPNManager(connection: mockConnection)
    
    // 调用待测试的业务逻辑(比如触发connection的启动方法)
    try? mockManager.connection.startVPNTunnel(options: nil)
    
    // 断言方法被正确调用
    XCTAssertTrue(mockConnection.startVPNTunnelCalled)
    
    // 测试状态变化逻辑
    mockConnection.status = .connected
    XCTAssertEqual(mockConnection.status, .connected)
}

通过这套方案,你可以完全脱离真实的NetworkExtension框架,对依赖NEVPNManager的代码进行独立单元测试。

内容的提问来源于stack exchange,提问作者Houman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:04:51