Зачем использовать протоколы и моки для тестирования в iOS
Что проверяют
Понимаете ли вы, как сделать код тестируемым в Swift, где нет мокинга уровня Mockito. Следом обычно просят показать, как протестировать экран, который ходит в сеть, и чем мок отличается от стаба.
Как отвечать
Начните с причины. В Swift нет рефлексии, которая позволила бы на лету сгенерировать подменный объект, как это делает Mockito в Java, а OCMock работает только с наследниками NSObject. Поэтому подмена закладывается в дизайн: зависимость описывается протоколом, а реализация передаётся снаружи.
protocol ProfileService {
func loadProfile(id: String) async throws -> Profile
}
final class ProfileViewModel {
private let service: ProfileService
init(service: ProfileService) { self.service = service }
}В приложении передаётся реальный сервис на URLSession, в тесте объект, который вы полностью контролируете. Что это даёт:
- изоляция: тест не зависит от сети и сервера, проходит за миллисекунды и одинаково ведёт себя на CI;
- сценарии: легко проверить ошибку сети, пустой ответ, таймаут, которые на живом бэкенде воспроизвести трудно;
- проверка взаимодействия: подменный объект записывает вызовы, и видно, что при повторе запрос ушёл один раз.
Полезно назвать виды дублёров: стаб отдаёт заготовленный ответ, мок проверяет, что вызов был, фейк это рабочая упрощённая реализация, например хранилище в памяти. Моки пишут руками или генерируют: Sourcery, Mockolo, библиотеки на макросах Swift. Сетевой слой можно тестировать и без своего протокола, подменив транспорт подклассом URLProtocol. Сами тесты пишутся на XCTest или на Swift Testing с @Test и #expect.
Где спотыкаются
Заводят протокол на каждый класс, у которого одна реализация и нет внешних зависимостей. Кода становится больше, пользы нет: протокол нужен на границе с сетью, хранилищем, временем, системными API.
Обращаются к URLSession.shared или UserDefaults.standard прямо внутри класса. Такой код не протестировать без переписывания.
Кладут в мок логику, и тест начинает проверять мок, а не код. Обратный перекос: проверять каждый вызов, из-за чего тесты ломаются от любого рефакторинга.