如何模拟 DataTaskPublisher?

G. *_*arc 5 unit-testing swift combine

我正在尝试使用 URLSession.DataTaskPublisher 为我的 API 编写一些单元测试。我在 Stackoverflow 上发现了一个已经存在的问题,但我正在努力使用建议的解决方案来实现一个工作班级。

这是现有的问题:如何模拟 URLSession.DataTaskPublisher

protocol APIDataTaskPublisher {
    func dataTaskPublisher(for request: URLRequest) -> URLSession.DataTaskPublisher
}

class APISessionDataTaskPublisher: APIDataTaskPublisher {
    func dataTaskPublisher(for request: URLRequest) -> URLSession.DataTaskPublisher {
        return session.dataTaskPublisher(for: request)
    }

    var session: URLSession

    init(session: URLSession = URLSession.shared) {
        self.session = session
    }
}

class URLSessionMock: APIDataTaskPublisher {
    func dataTaskPublisher(for request: URLRequest) -> URLSession.DataTaskPublisher {
        // How can I return a mocked URLSession.DataTaskPublisher here?
    }
}
Run Code Online (Sandbox Code Playgroud)

我的 API 然后像这样使用上面的:

class MyAPI {
    /// Shared URL session
    private let urlSession: APIDataTaskPublisher

    init(urlSession: APIDataTaskPublisher = APISessionDataTaskPublisher(session: URLSession.shared)) {
        self.urlSession = urlSession
    }
}
Run Code Online (Sandbox Code Playgroud)

我不知道的是如何实现 URLSessionMock.dataTaskPublisher()。

rob*_*off 15

模拟可能会更简单DataTaskPublisher。你真的关心出版商是否是DataTaskPublisher? 可能不是。什么你可能关心的是得到同样OutputFailure类型的DataTaskPublisher。因此,将您的 API 更改为仅指定:

protocol APIProvider {
    typealias APIResponse = URLSession.DataTaskPublisher.Output
    func apiResponse(for request: URLRequest) -> AnyPublisher<APIResponse, URLError>
}
Run Code Online (Sandbox Code Playgroud)

符合URLSession它的生产用途:

extension URLSession: APIProvider {
    func apiResponse(for request: URLRequest) -> AnyPublisher<APIResponse, URLError> {
        return dataTaskPublisher(for: request).eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您的模拟可以以任何方便的方式创建发布者。例如:

struct MockAPIProvider: APIProvider {
    func apiResponse(for request: URLRequest) -> AnyPublisher<APIResponse, URLError> {
        let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil)!
        let data = "Hello, world!".data(using: .utf8)!
        return Just((data: data, response: response))
            .setFailureType(to: URLError.self)
            .eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 真的。您还可以说“Result.Publisher((data: data, response: response))”和“Result.Publisher(URLError(.timedOut))”,它们甚至更短。 (4认同)
  • 您可以使用 Result.success((data: data, response: response)).publisher 来代替 `Just(_:).setFailureType(to:)`,从而推断出失败类型。它还适用于测试错误条件:`Result.failure(URLError(.timedOut)).publisher`,或任何需要的 URL 错误。 (3认同)

Asp*_*eri 2

如果您将要测试的每个 API 调用存储在 UT 捆绑存根 JSON(XML 或其他内容)中,那么最简单的模拟代码可能如下所示

class URLSessionMock: APIDataTaskPublisher {
    func dataTaskPublisher(for request: URLRequest) -> URLSession.DataTaskPublisher {

        // here might be created a map of API URLs to cached stub replies
        let stubReply = request.url?.lastPathComponent ?? "stub_error"
        return URLSession.shared.dataTaskPublisher(for: Bundle(for: type(of: self)).url(forResource: stubReply, withExtension: "json")!)
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,请改为调用网络服务器,您的发布者是使用具有已知数据的本地存储资源的 URL 创建的,因此您可以验证所有工作流程。