Невозможно преобразовать значение типа 'MyEnum'к ожидаемому типу аргумента' MyEnum <_> ' - PullRequest
0 голосов
/ 29 января 2019

У меня есть сетевой уровень, работающий с генериками, и я использую протоколы, чтобы проверить его позже.Я следовал этому уроку https://medium.com/thecocoapps/network-layer-in-swift-4-0-972bf2ea5033

Это мой макет для тестирования:

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<_>' в этой строке: завершение (NetworkResponse.success (T))

Если я отправлю это для успешного завершения, он скомпилирует: try? JSONDecoder().decode(T.self, from: data!) (фиктивные данные, которые я создал, используя encode и мою модель), но вылетает при достижении моей модели, потому что равен нулю, несмотря на то, что я закодировалиспользование 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 в моем тесте, потому что это настройка запроса с помощьюh конечная точка, заголовки, тело, идентификатор и т. д. Но это протокол, который я создал:

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 и использует URLSession.shared для выполнения dataTask, когданекоторые viewModel вызывают запрос с подходящей моделью.

Я использую для тестирования с протоколами и конкретной моделью, но с обобщениями показывает мне эту ошибку.Как мне добиться, чтобы фиктивный провайдер использовал дженерики, чтобы я мог протестировать любую модель представления, которая делает вызов в сеть, используя разные типы моделей (заглушки).

1 Ответ

0 голосов
/ 30 января 2019

Ошибка возникает из-за того, что NetworkResponse ожидает экземпляр T, в то время как макет пытается предоставить фактический T.

Итак, вам нужно каким-то образом предоставить экземпляр, однако это не может бытьгенерируется макетом, поскольку в нем недостаточно информации о том, как создать экземпляр.

Я рекомендую вводить значение успеха извне при создании макета.Вы можете сделать это либо сделав общий класс mock, либо сделав общий тип enum 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")))
        }

    }
}
...