我只是最近才意识到对URLProtocol
进行子类化以支持网络调用测试的技术(以前我使用过类似于Rob Napier's "Mockery of protocols"的东西)。
但是,尝试将其应用于async/await
调用会导致崩溃。这段代码说明了问题-基于完成的测试将通过,异步测试将崩溃(例如EXC_BREAKPOINT (code=1, subcode=0x1a587a0f0)
)
import XCTest
import Foundation
final class TestableNetworkingTests: XCTestCase {
override func setUp() {
URLProtocol.registerClass(StubUrlProtocol.self)
}
override class func tearDown() {
URLProtocol.unregisterClass(StubUrlProtocol.self)
}
// URL doesn't matter - request never actually gets sent
static let request = URLRequest(url: URL(string: "http://example.com/")!)
func testCompletion() throws {
StubUrlProtocol.observer = { request -> (URLResponse?, Data?) in
return (nil, "Completion Hello".data(using: .utf8)!)
}
let responded = expectation(description: "Should have responded")
var received: Data?
let task = URLSession.shared.dataTask(with: Self.request) { data, response, error in
received = data
responded.fulfill()
}
task.resume()
wait(for: [responded], timeout: 5)
let message = String(data: received ?? Data(), encoding: .utf8)
XCTAssertEqual(message, "Completion Hello")
}
func testAsync() async throws {
StubUrlProtocol.observer = { request -> (URLResponse?, Data?) in
return (nil, "Awaited hello".data(using: .utf8)!)
}
// URLProtocol.registerClass makes this feel unnecessary,
// but plenty of articles recommend setting .protocolClasses
let cfg = URLSessionConfiguration.ephemeral
cfg.protocolClasses = [StubUrlProtocol.self]
let session = URLSession(configuration: cfg)
let (received, _) = try await session.data(for: Self.request)
let message = String(data: received, encoding: .utf8)
XCTAssertEqual(message, "Awaited hello")
}
}
class StubUrlProtocol: URLProtocol {
static var observer: ((URLRequest) throws -> (URLResponse?, Data?))?
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canInit(with task: URLSessionTask) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
do {
guard let (response, data) = try Self.observer?(request) else {
return
}
if let response {
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
}
if let data {
client?.urlProtocol(self, didLoad: data)
}
client?.urlProtocolDidFinishLoading(self)
} catch {
client?.urlProtocol(self, didFailWithError: error)
}
}
override func stopLoading() { }
}
虽然Transport
方法为我们提供了使存根网络调用完全同步的选项,消除了对任何期望的所有需求,但我不确定这在async/await
世界中是否可行。
1条答案
按热度按时间pgpifvop1#
在自定义
URLProtocol
类中,将以下行添加到startLoading
方法中: