swift 使用`URLProtocol`子类测试async/await URLSession调用

5us2dqdw  于 2023-05-16  发布在  Swift
关注(0)|答案(1)|浏览(206)

我只是最近才意识到对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世界中是否可行。

pgpifvop

pgpifvop1#

在自定义URLProtocol类中,将以下行添加到startLoading方法中:

self.client?.urlProtocol(self, didReceive: HTTPURLResponse(), cacheStoragePolicy: .allowed)

相关问题