Ответ
Протоколы являются ключевым инструментом зависимостей (Dependency Injection) и изоляции тестируемого модуля. Они позволяют заменить реальные, сложные или недетерминированные зависимости на контролируемые заглушки (mocks/stubs).
Конкретные преимущества:
- Абстракция зависимостей: Тестируемый класс зависит не от конкретной реализации, а от протокола.
- Создание моков: Легко создать «подставную» реализацию протокола для тестов.
- Проверка взаимодействий: Мок может фиксировать факт и параметры вызовов своих методов.
Пример архитектуры с протоколом:
// 1. Протокол зависимости
protocol DataFetcherProtocol {
func fetchItems(completion: @escaping (Result<[Item], Error>) -> Void)
}
// 2. Реальная реализация (не используется в тестах)
class NetworkDataFetcher: DataFetcherProtocol { ... }
// 3. Мок-реализация для тестов
class MockDataFetcher: DataFetcherProtocol {
var fetchItemsWasCalled = false
var forcedResult: Result<[Item], Error>?
func fetchItems(completion: @escaping (Result<[Item], Error>) -> Void) {
fetchItemsWasCalled = true
if let result = forcedResult {
completion(result)
}
}
}
// 4. Тестируемый ViewModel
class ItemsViewModel {
private let fetcher: DataFetcherProtocol // Зависимость через протокол
init(fetcher: DataFetcherProtocol) { self.fetcher = fetcher }
func loadData() { fetcher.fetchItems { ... } }
}
// 5. Сам тест
func testLoadDataCallsFetcher() {
let mockFetcher = MockDataFetcher()
mockFetcher.forcedResult = .success([])
let viewModel = ItemsViewModel(fetcher: mockFetcher)
viewModel.loadData()
XCTAssertTrue(mockFetcher.fetchItemsWasCalled)
}
Такой подход делает тесты быстрыми (нет реальных сетевых запросов), стабильными (нет зависимости от внешних сервисов) и сфокусированными на логике тестируемого модуля.