如何模拟 URLSession.DataTaskPublisher
Posted
技术标签:
【中文标题】如何模拟 URLSession.DataTaskPublisher【英文标题】:How to mock URLSession.DataTaskPublisher 【发布时间】:2020-04-18 16:10:41 【问题描述】:如何模拟URLSession.DataTaskPublisher
?我有一个类 Proxy
需要注入 URLSessionProtocol
protocol URLSessionProtocol
func loadData(from url: URL) -> URLSession.DataTaskPublisher
class Proxy
private let urlSession: URLSessionProtocol
init(urlSession: URLSessionProtocol)
self.urlSession = urlSession
func get(url: URL) -> AnyPublisher<Data, ProxyError>
// Using urlSession.loadData(from: url)
此代码最初与带有完成处理程序的传统版本的URLSession
一起使用。这是完美的,因为我可以轻松地模拟 URLSession
来进行像 Sundell 的解决方案一样的测试:Mocking in Swift。
是否可以对组合框架做同样的事情?
【问题讨论】:
【参考方案1】:与您可以注入URLSessionProtocol
来模拟具体会话的方式相同,您也可以注入模拟的Publisher
。例如:
let mockPublisher = Just(MockData()).eraseToAnyPublisher()
但是,根据您对这个发布者所做的事情,您可能需要解决组合异步发布者的一些奇怪问题,请参阅这篇文章进行更多讨论:
Why does Combine's receive(on:) operator swallow errors?
【讨论】:
我理解这个概念,但是您将如何实现呢?我正在努力在我的模拟 loadData 函数中返回一个 URLSession.DataTaskPublisher 。您能否提供有关此解决方案的更多详细信息。【参考方案2】:由于DataTaskPublisher
使用URLSession
创建它,你可以模拟它。我最终创建了一个URLSession
子类,覆盖dataTask(...)
以返回一个URLSessionDataTask
子类,我提供了我需要的数据/响应/错误...
class URLSessionDataTaskMock: URLSessionDataTask
private let closure: () -> Void
init(closure: @escaping () -> Void)
self.closure = closure
override func resume()
closure()
class URLSessionMock: URLSession
var data: Data?
var response: URLResponse?
var error: Error?
override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
let data = self.data
let response = self.response
let error = self.error
return URLSessionDataTaskMock
completionHandler(data, response, error)
那么显然你只希望你的网络层使用这个URLSession
,我去工厂做这个:
protocol DataTaskPublisherFactory
func make(for request: URLRequest) -> URLSession.DataTaskPublisher
然后在你的网络层:
func performRequest<ResponseType>(_ request: URLRequest) -> AnyPublisher<ResponseType, APIError> where ResponseType : Decodable
Just(request)
.flatMap
self.dataTaskPublisherFactory.make(for: $0)
.mapError APIError.urlError($0)
.eraseToAnyPublisher()
现在您可以使用 URLSession
子类在测试中传递一个模拟工厂(这个断言 URLError
s 映射到自定义错误,但您也可以断言给定数据/响应的其他一些条件):
func test_performRequest_URLSessionDataTaskThrowsError_throwsAPIError()
let session = URLSessionMock()
session.error = TestError.test
let dataTaskPublisherFactory = mock(DataTaskPublisherFactory.self)
given(dataTaskPublisherFactory.make(for: any())) ~>
session.dataTaskPublisher(for: $0)
let api = API(dataTaskPublisherFactory: dataTaskPublisherFactory)
let publisher: AnyPublisher<TestCodable, APIError> =
api.performRequest(URLRequest(url: URL(string: "www.someURL.com")!))
let _ = publisher.sink(receiveCompletion:
switch $0
case .failure(let error):
XCTAssertEqual(error, APIError.urlError(URLError(_nsError: NSError(domain: "NSURLErrorDomain", code: -1, userInfo: nil))))
case .finished:
XCTFail()
) _ in
其中一个问题是 URLSession
init()
在 ios 13 中已被弃用,因此您必须在测试中接受警告。如果有人能找到解决办法,我将不胜感激。
(注意:我使用 Mockingbird 进行模拟)。
【讨论】:
iOS13 弃用了正常的 DataTaskinit()
,这会导致您的子类模拟 'init()' was deprecated in iOS 13.0: Please use -[NSURLSession dataTaskWithRequest:] or other NSURLSession methods to create instances
发出警告【参考方案3】:
测试客户端的最佳方法是使用URLProtocol
。
https://developer.apple.com/documentation/foundation/urlprotocol
您可以在她在云端执行真正的请求之前拦截您的所有请求,从而创造您的期望。一旦你完成了你的期望,她就会被摧毁,所以你永远不会提出真正的要求。 测试更可靠、更快速,您可以掌控一切!
这里有一个小例子:https://www.hackingwithswift.com/articles/153/how-to-test-ios-networking-code-the-easy-way
但它比这更强大,你可以做任何你想做的事情:检查你的事件/分析......
希望对你有帮助!
【讨论】:
以上是关于如何模拟 URLSession.DataTaskPublisher的主要内容,如果未能解决你的问题,请参考以下文章