如何在 Swift 中存根 URLSession?

How to stub URLSession in Swift?

我一直在关注这个 tutorial to stub out URLSession. The example 是通过创建一个协议并扩展现有的 URLSession 来完成的。

protocol URLSessionProtocol {
    typealias DataTaskResult = (Data?, URLResponse?, Error?) -> Void
    func dataTask(with request: NSURLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol
}

extension URLSession: URLSessionProtocol {
    func dataTask(with request: NSURLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol {
        return dataTask(with: request, completionHandler: completionHandler) as URLSessionDataTaskProtocol
    }
}

单元测试按预期工作。但是当我尝试 运行 真实的东西时, URLSession -> datatask() 进入无限循环并崩溃。似乎是 datatask() 正在调用自己。

请问我忽略了什么?

更新:

protocol URLSessionDataTaskProtocol {
    var originalRequest: URLRequest? { get }
    func resume()
}

extension URLSessionDataTask: URLSessionDataTaskProtocol {}

我终于找到了解决办法。这很迷人,因为我们只见树木不见森林。有两个问题:

1) Swift 4 似乎已将 dataTask(with: NSURLRequest) 的签名更改为 dataTask(with: URLRequest)

因此,我开篇问题中的那一行只会与协议的 func 签名相匹配,它永远不会碰到 URLSession 内的 dataTask,因此会出现无限循环。为了解决这个问题,我不得不将 NSURLRequest 更改为 URLRequest 并相应地重构代码。

2) 签名仍然模糊,因此最好先将结果存储为 dataTask,然后转换为 URLSessionDataTask,然后 return 变量。

Swift 4 的新重构代码:

typealias DataTaskResult = (Data?, URLResponse?, Error?) -> Void

protocol URLSessionProtocol {
    func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol
}

extension URLSession: URLSessionProtocol {
    func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol {
        let task:URLSessionDataTask = dataTask(with: request, completionHandler: {
            (data:Data?, response:URLResponse?, error:Error?) in completionHandler(data,response,error) }) as URLSessionDataTask
        return task
    }
}

我还发现我必须将 URLSession.shared 作为单例而不是 URLSession() 注入,否则它可能会崩溃。

来到这里是为了了解如何模拟 URLSession 任务,例如 URLSessionDataTask?

大约 Swift 5,模拟 URLSession 用来发送请求的 URLProtocol 更容易

Unit Testing URLSession using URLProtocol.