我有一个使用泛型的网络层,我正在使用协议,所以我可以稍后对其进行测试。我已经按照本教程https://medium.com/thecocoapps/network-layer-in-swift-4-0-972bf2ea5033
这是我用于测试的 Mock:
import Foundation
@testable import TraktTest
class MockUrlSessionProvider: ProviderProtocol {
enum Mode {
case success
case empty
case fail
}
private var mode: Mode
init(mode: Mode) {
self.mode = mode
}
func request<T>(type: T.Type, service: ServiceProtocol, completion: @escaping (NetworkResponse<T>) -> Void) where T: Decodable {
switch mode {
case .success: completion(NetworkResponse.success(T))
case .empty: completion(.failure(.noData))
case .fail: completion(.failure(.unknown("Error")))
}
}
}
我收到错误:Cannot convert value of type 'NetworkResponse<T.Type>' to expected argument type 'NetworkResponse<_>'
在这一行:completion(NetworkResponse.success(T))
如果我将它发送到我的完成成功,它会编译:(try? JSONDecoder().decode(T.self, from: data!)
我使用编码和我的模型创建的虚拟数据),但是在到达我的模型时崩溃,因为尽管我已经使用 JSONEncoder() 编码了正确的模型,但它仍然为零。
我认为它有效,因为我在我的类中使用的逻辑与在我的应用程序中实现 ProviderProtocol 的逻辑相同:
final class URLSessionProvider: ProviderProtocol {
private var session: URLSessionProtocol
init(session: URLSessionProtocol = URLSession.shared) {
self.session = session
}
func request<T>(type: T.Type, service: ServiceProtocol, completion: @escaping (NetworkResponse<T>) -> Void) where T: Decodable {
let request = URLRequest(service: service)
session.dataTask(request: request) { [weak self] data, response, error in
let httpResponse = response as? HTTPURLResponse
self?.handleDataResponse(data: data, response: httpResponse, error: error, completion: completion)
}.resume()
}
private func handleDataResponse<T: Decodable>(data: Data?, response: HTTPURLResponse?, error: Error?, completion: (NetworkResponse<T>) -> Void) {
guard error == nil else { return completion(.failure(.unknown(error?.localizedDescription ?? "Error"))) }
guard let response = response else { return completion(.failure(.unknown("no_response".localized()))) }
switch response.statusCode {
case 200...299:
guard let data = data, let model = try? JSONDecoder().decode(T.self, from: data) else { return completion(.failure(.noData)) }
completion(.success(model))
default: completion(.failure(.unknown("no_response".localized())))
}
}
}
URLSessionProtocol 只是一个协议,它的 dataTask 方法与 URLSession.shared 中的方法相同(接收 URLRequest 并在完成时返回数据、响应和错误)。
我的网络响应是几个枚举:
enum NetworkResponse<T> {
case success(T)
case failure(NetworkError)
}
enum NetworkError {
case unknown(String)
case noData
}
我的提供者协议只有一个使用泛型发出请求的功能:
protocol ProviderProtocol {
func request<T>(type: T.Type, service: ServiceProtocol, completion: @escaping(NetworkResponse<T>) -> Void) where T: Decodable
}
我认为我不需要在我的测试中使用 ServiceProtocol 因为是使用端点、标头、正文、id 等设置请求。但这是我创建的协议:
typealias Headers = [String: String]
typealias Parameters = [String: Any]
protocol ServiceProtocol {
func baseURL() -> URL
var path: String? { get }
var id: String? { get }
var method: HTTPMethod { get }
var task: Task { get }
var headers: Headers? { get }
var parametersEncoding: ParametersEncoding { get }
}
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
}
enum Task {
case requestPlain
case requestParameters(Parameters)
}
enum ParametersEncoding {
case url
case json
}
在我的应用程序中,我有一个实现 ProviderProtocol 的类,并在某些 viewModel 使用适当的模型调用请求时使用 URLSession.shared 来创建 dataTask。
我习惯于使用协议和特定模型进行测试,但使用泛型向我展示了该错误。如何使用泛型实现模拟提供程序,以便我可以测试任何使用不同类型模型(存根)调用网络的视图模型。
发生错误是因为NetworkResponse
需要 的实例T
,而模拟尝试提供实际的T
.
所以,你需要以某种方式提供一个实例,但是这不能由模拟生成,因为它没有关于如何构造实例的足够信息。
我建议在创建模拟时从外部注入成功值。您可以通过使模拟类通用或通过使Mode
枚举通用来做到这一点。下面是后者的示例实现:
class MockUrlSessionProvider: ProviderProtocol {
// making the enum generic, to support injecting the success value
enum Mode<T> {
case success(T)
case empty
case fail
}
// need to have this as `Any` to cover all possible T generic arguments
private var mode: Any
// however the initializer can be very specific
init<T>(mode: Mode<T>) {
self.mode = mode
}
func request<T>(type: T.Type, service: ServiceProtocol, completion: @escaping (NetworkResponse<T>) -> Void) where T: Decodable {
// if the mock was not properly configured, do nothing
guard let mode = mode as? Mode<T> else { return }
// alternatively you force cast and have the unit test crash, this should help catching early configuration issues
// let mode = mode as! Mode<T>
switch mode {
case let .success(value): completion(NetworkResponse.success(value))
case .empty: completion(.failure(.noData))
case .fail: completion(.failure(.unknown("Error")))
}
}
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句