Как мокать сетевые вызовы
Что проверяют
Умеете ли вы тестировать код, который ходит в сеть, так, чтобы тесты не зависели от сервера и не падали через раз. Следом обычно спрашивают, где проходит граница мока: подменяете ли вы весь сетевой слой или только транспорт под URLSession.
Как отвечать
Назовите два уровня подмены и когда какой брать:
- протокол над сетью: код зависит не от
URLSession, а от своего протокола, например с методомfunc data(for request: URLRequest) async throws -> (Data, URLResponse). В проде его реализуетURLSessionчерез расширение, в тестах фейк, который возвращает заготовленный ответ или бросает ошибку. Просто, быстро, подходит для тестов ViewModel и сервисов; URLProtocol: подкласс перехватывает запросы на уровне загрузки и отдаёт своиData,HTTPURLResponseили ошибку. Регистрируется черезURLSessionConfiguration.ephemeralиprotocolClasses. Так проверяется весь путь целиком: сборкаURLRequest, заголовки, коды ответа, декодирование.
Ответы держите в JSON-фикстурах внутри тестового бандла, а не строками в коде. Проверяйте не только успех: 401, 500, пустое тело, битый JSON, URLError(.notConnectedToInternet), таймаут.
Из готовых решений поверх URLProtocol есть OHHTTPStubs и Mocker, у Moya свои заглушки через stubClosure. Подключаются они через Swift Package Manager. Для UI-тестов приложение запускают с аргументом запуска, по которому оно собирает клиент с подменённым транспортом.
Где спотыкаются
Мокают URLSession.shared. Конфигурацию общей сессии не задать, остаётся только глобальный URLProtocol.registerClass, который влияет на всё приложение и все тесты сразу. Сессию надо внедрять, а не брать синглтон внутри сервиса.
Хранят обработчик в статическом свойстве MockURLProtocol и запускают тесты в Swift Testing, где они по умолчанию идут параллельно. Ответы одного теста прилетают в другой, и тесты начинают мигать.
Проверяют тело запроса через request.httpBody внутри URLProtocol и получают nil: к этому моменту тело уже переложено в httpBodyStream, читать надо оттуда.