如何存根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
相关产品推荐
相关产品推荐

