Напишите async-функцию запроса с протокол-внедряемым транспортом, тестируемую без сети
Напишите async-функцию, которая загружает и декодирует Decodable-модель, с транспортом, внедрённым через протокол, чтобы модульному тесту не требовалась сеть.
Требования:
URLSession должен уметь ему соответствовать.
- Объявите протокол
Transportсasync-методом, возвращающим(Data, URLResponse); - Функция throws при статусе вне 2xx и декодирует тело через
JSONDecoder. - Никакого
URLSession.sharedвнутри функции — транспорт передаётся параметром.
protocol Transport {
// объявите async-метод транспорта
}
func fetch<T: Decodable>(_ request: URLRequest, using transport: Transport) async throws -> T {
// ваш код здесь
}
Допишите реализацию.
Опишите протокол Transport — func data(for: URLRequest) async throws -> (Data, URLResponse) — и сделайте URLSession его соответствием. Функция строит запрос, делает await транспорта, проверяет статус и декодирует через JSONDecoder. В проде внедряется реальный URLSession; в тесте — заглушка с готовыми данными, поэтому тест идёт офлайн.
- ✗Зовут
URLSession.sharedнапрямую, не оставляя что подменить - ✗Не проверяют HTTP-статус перед декодированием тела
- ✗Делают функцию не-дженериком, и под каждую модель нужна своя копия
- →Как подкласс
URLProtocolперехватывает запросы без реального сервера? - →Почему внедрение через параметр тестировать проще, чем синглтон-транспорт?
Транспорт становится протоколом с одним async-методом, которому URLSession уже соответствует, поэтому тест подменяет его заглушкой без сети:
protocol Transport {
func data(for request: URLRequest) async throws -> (Data, URLResponse)
}
extension URLSession: Transport {}
func fetch<T: Decodable>(_ request: URLRequest, using transport: Transport) async throws -> T {
let (data, response) = try await transport.data(for: request)
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(T.self, from: data)
}
// Test double — no network:
struct StubTransport: Transport {
let result: (Data, URLResponse)
func data(for request: URLRequest) async throws -> (Data, URLResponse) { result }
}
Проверка статуса стоит перед decode, поэтому тело ошибки не парсится как модель. В тесте передайте StubTransport с заранее заданными Data и HTTPURLResponse — вызов детерминирован и офлайн.