如何在 NEVPNManager 中存根连接 属性 (NEVPNConnection)?
How to stub the connection property (NEVPNConnection) in NEVPNManager?
我想通过协议扩展现有的 NetworkExtension
类,以便对我的代码进行单元测试。
我首先为 NEVPNManager
创建了协议
protocol NEVPNManagerProtocol {
var connection : ConnectionProtocol { get } // <-- Doesn't work
func loadFromPreferences(completionHandler: @escaping (Error?) -> Swift.Void)
func saveToPreferences(completionHandler: ((Error?) -> Swift.Void)?)
}
extension NEVPNManager: NEVPNManagerProtocol {}
然后 connection
属性 的单独协议将其存根。
protocol ConnectionProtocol {
var status: NEVPNStatus { get }
func stopVPNTunnel()
func startVPNTunnel() throws
}
extension NEVPNConnection : ConnectionProtocol {}
在 NEVPNManager 内部,我可以看到我正在确认 属性 签名,但 Xcode 不相信我并声称:
Type 'NEVPNManager' does not conform to protocol
'NEVPNManagerProtocol'
它会尝试像这样自动更正它:
extension NEVPNManager: NEVPNManagerProtocol {
var connection: ConnectionProtocol {
<#code#>
}
}
但是检查 NEVPNManager
中的签名,对我来说似乎是正确的:
/*!
* @property connection
* @discussion The NEVPNConnection object used for controlling the VPN tunnel.
*/
@available(iOS 8.0, *)
open var connection: NEVPNConnection { get }
有什么建议吗?
模拟这个很棘手,因为 Apple 控制着 NEVPNManager
及其 NEVPNConnection
.
的实例化
您看到的错误是因为您正在尝试重新定义 connection
属性,而您不能这样做。 NEVPNManager
已经有 connection
属性 类型 NEVPNConnection
。
我们可以使用您的第一个协议(已修改)和几个模拟 classes.
的组合来模拟 connection
属性
首先,协议需要稍微调整一下:
protocol NEVPNManagerProtocol {
var connection : NEVPNConnection { get } // <-- has to be this type
func loadFromPreferences(completionHandler: @escaping (Error?) -> Swift.Void)
func saveToPreferences(completionHandler: ((Error?) -> Swift.Void)?)
}
extension NEVPNManager: NEVPNManagerProtocol {}
接下来,我们需要一个模拟连接 class,因为 connection
属性 必须是 class 类型NEVPNConnection
,或从该类型继承的一个。在这里引入协议似乎没有太大好处,因为我们正在尝试模拟 class 的行为,我们可以更直接地使用模拟来完成。
class MockNEVPNConnection: NEVPNConnection {
override var status: NEVPNStatus {
return NEVPNStatus.connected //or whatever
}
override func stopVPNTunnel() {
print("MockNEVPNConnection.stopVPNTunnel")
}
override func startVPNTunnel() throws {
print("MockNEVPNConnection.startVPNTunnel")
}
}
最后,我们需要一个模拟管理器 class 来 returns 一个模拟连接。使用模拟管理器是我能够注入模拟连接的唯一方法。
模拟管理器符合 NEVPNManagerProtocol
和 returns 我们的模拟连接对象。 (注意:当尝试直接从 NEVPNManager
继承时,我的 playground 在实例化模拟时崩溃了。)
class MockNEVPNManager: NEVPNManagerProtocol {
var connection: NEVPNConnection {
return MockNEVPNConnection()
}
func loadFromPreferences(completionHandler: @escaping (Error?) -> Swift.Void) {
print("MockNEVPNManager.loadFromPreferences")
}
func saveToPreferences(completionHandler: ((Error?) -> Swift.Void)?) {
print("MockNEVPNManager.saveToPreferences")
}
}
客户端 class 必须采用类型 NEVPNManagerProtocol
而不是 NEVPNManager
的对象,以便我们可以将模拟传递给它。
class MyClient {
let manager: NEVPNManagerProtocol
init(manager: NEVPNManagerProtocol) {
self.manager = manager
}
}
在现实生活中,我们可以将真正的经理传递给我们的客户:
let myClient = MyClient(manager: NEVPNManager.shared())
在我们的测试中,我们可以通过模拟:
let myMockedClient = MyClient(manager: MockNEVPNManager())
并在连接上调用方法:
try? myMockedClient.manager.connection.startVPNTunnel()
//prints "MockNEVPNConnection.startVPNTunnel"
我想通过协议扩展现有的 NetworkExtension
类,以便对我的代码进行单元测试。
我首先为 NEVPNManager
protocol NEVPNManagerProtocol {
var connection : ConnectionProtocol { get } // <-- Doesn't work
func loadFromPreferences(completionHandler: @escaping (Error?) -> Swift.Void)
func saveToPreferences(completionHandler: ((Error?) -> Swift.Void)?)
}
extension NEVPNManager: NEVPNManagerProtocol {}
然后 connection
属性 的单独协议将其存根。
protocol ConnectionProtocol {
var status: NEVPNStatus { get }
func stopVPNTunnel()
func startVPNTunnel() throws
}
extension NEVPNConnection : ConnectionProtocol {}
在 NEVPNManager 内部,我可以看到我正在确认 属性 签名,但 Xcode 不相信我并声称:
Type 'NEVPNManager' does not conform to protocol 'NEVPNManagerProtocol'
它会尝试像这样自动更正它:
extension NEVPNManager: NEVPNManagerProtocol {
var connection: ConnectionProtocol {
<#code#>
}
}
但是检查 NEVPNManager
中的签名,对我来说似乎是正确的:
/*!
* @property connection
* @discussion The NEVPNConnection object used for controlling the VPN tunnel.
*/
@available(iOS 8.0, *)
open var connection: NEVPNConnection { get }
有什么建议吗?
模拟这个很棘手,因为 Apple 控制着 NEVPNManager
及其 NEVPNConnection
.
您看到的错误是因为您正在尝试重新定义 connection
属性,而您不能这样做。 NEVPNManager
已经有 connection
属性 类型 NEVPNConnection
。
我们可以使用您的第一个协议(已修改)和几个模拟 classes.
的组合来模拟connection
属性
首先,协议需要稍微调整一下:
protocol NEVPNManagerProtocol {
var connection : NEVPNConnection { get } // <-- has to be this type
func loadFromPreferences(completionHandler: @escaping (Error?) -> Swift.Void)
func saveToPreferences(completionHandler: ((Error?) -> Swift.Void)?)
}
extension NEVPNManager: NEVPNManagerProtocol {}
接下来,我们需要一个模拟连接 class,因为 connection
属性 必须是 class 类型NEVPNConnection
,或从该类型继承的一个。在这里引入协议似乎没有太大好处,因为我们正在尝试模拟 class 的行为,我们可以更直接地使用模拟来完成。
class MockNEVPNConnection: NEVPNConnection {
override var status: NEVPNStatus {
return NEVPNStatus.connected //or whatever
}
override func stopVPNTunnel() {
print("MockNEVPNConnection.stopVPNTunnel")
}
override func startVPNTunnel() throws {
print("MockNEVPNConnection.startVPNTunnel")
}
}
最后,我们需要一个模拟管理器 class 来 returns 一个模拟连接。使用模拟管理器是我能够注入模拟连接的唯一方法。
模拟管理器符合 NEVPNManagerProtocol
和 returns 我们的模拟连接对象。 (注意:当尝试直接从 NEVPNManager
继承时,我的 playground 在实例化模拟时崩溃了。)
class MockNEVPNManager: NEVPNManagerProtocol {
var connection: NEVPNConnection {
return MockNEVPNConnection()
}
func loadFromPreferences(completionHandler: @escaping (Error?) -> Swift.Void) {
print("MockNEVPNManager.loadFromPreferences")
}
func saveToPreferences(completionHandler: ((Error?) -> Swift.Void)?) {
print("MockNEVPNManager.saveToPreferences")
}
}
客户端 class 必须采用类型 NEVPNManagerProtocol
而不是 NEVPNManager
的对象,以便我们可以将模拟传递给它。
class MyClient {
let manager: NEVPNManagerProtocol
init(manager: NEVPNManagerProtocol) {
self.manager = manager
}
}
在现实生活中,我们可以将真正的经理传递给我们的客户:
let myClient = MyClient(manager: NEVPNManager.shared())
在我们的测试中,我们可以通过模拟:
let myMockedClient = MyClient(manager: MockNEVPNManager())
并在连接上调用方法:
try? myMockedClient.manager.connection.startVPNTunnel()
//prints "MockNEVPNConnection.startVPNTunnel"