Давайте воспользуемся XCTest Framework в iOS для тестирования кодовой базы Combine.
Combine — это среда реактивного программирования, выпущенная Apple в iOS 13, которая позволяет нам работать с асинхронными потоками данных.
По словам Apple, Combine позволяет нам:
Настройте обработку асинхронных событий, комбинируя операторы обработки событий.
В этой статье мы рассмотрим, как выполнять модульное тестирование кода на основе Combine с использованием платформы XCTest Apple.
Для этого мы напишем модульные тесты для примера приложения, которое будет отображать список заданий. Основные классы, которые мы будем использовать:
Job: представляет объект задания.  Наша модель.
JobsViewModel: Отвечает за всю логику презентации.  Мы будем работать с двумя состояниями просмотра: Populated и Empty.
ПротоколJobClientProtocol будет служить фасадом для нашей сетевой логики.
В вашем приложении должен быть соответствующий ему тип JobClient, но мы не будем его реализовывать в этой статье (тем более, что он нам не нужен):
Примечание. Для простоты JobsViewModel будет иметь JobClientProtocol в качестве зависимости (в идеале мы хотели бы иметь дополнительный уровень, чтобы наша модель представления не взаимодействовала напрямую с сетевым кодом).
Что будет протестировано
Нашей основной целью было бы проверить, правильно ли настроены все состояния представления в модели представления в соответствии с выбранными заданиями.
Два состояния, которые мы будем тестировать, и их условия:
- Populated: Если у нас есть одно или несколько загруженных заданий.
- Empty: Если нет выбранных заданий для отображения.
Создание макетов
Мы начнем с издевательства над нашим JobClientProtocol.  Это не должно быть сложной задачей, потому что наша модель представления зависит от абстракций (в данном случае от протокола), а не от конкретных типов.  Таким образом, MockJobClient будет выглядеть так:
Это позволит нам контролировать поведение сетевого клиента так, как мы хотим, давая нам большую гибкость для написания наших модульных тестов.
Примечание. Мы работаем с AnyPublisher, потому что эта форма стирания типов сохраняет абстракцию и, следовательно, скрывает базовую реализацию нашего сетевого клиента.
Определение наших сохраненных свойств
Сначала нам нужно будет объявить два хранимых свойства:
- viewModelToTest: Это будет экземпляр модели представления, который будет протестирован. Также известен как SUT (тестируемая система).
- mockJobClient: Экземпляр нашего фиктивного- JobClientProtocol.
Нам нужно хранимое свойство для каждого из них, потому что мы будем напрямую изменять mockJobClient для имитации различных сценариев, которые нам нужны (MockJobClient— этокласс,поэтому он будет передан как ссылка в инициализаторе модели представления, и любое обновление или изменение, выполненное в экземпляре MockJobClient, повлияет на поведение модели представления).
Наконец, у нас будет экземпляр AnyCancellableколлекции, потому что мы присоединим подписчика к издателям, возвращенным mockJobClient, и этого подписчика необходимо сохранить;  в противном случае он будет немедленно освобожден:
private var cancellables: Set<AnyCancellable> = []
Написание наших модульных тестов
Чтобы выполнять наши модульные тесты, нам нужно подписаться на издателя viewState в JobsViewModel.  Для этого мы будем использовать метод sink, который будет прикреплять подписчика к viewState, используя поведение на основе замыкания:
viewModelToTest.$viewState.dropFirst().sink { state in
    // Evaluate state here
}.store(in: &cancellables)
Следующим шагом является имитация ответов сетевых клиентов.  Мы можем легко сделать это, используя наш MockJobClient.  Чтобы смоделировать пустой ответ, мы должны сделать:
mockJobClient.fetchJobsResult = Result.success([]).publisher.eraseToAnyPublisher()
И для имитации заполненного ответа:
let jobsToTest = [Job(id: "1", title: "title", description: "desc")] mockJobClient.fetchJobsResult = Result.success(jobsToTest).publisher.eraseToAnyPublisher()
Последним шагом будет вызов метода loadJobs, который запустит процесс извлечения с помощью MockJobClient:
viewModelToTest.loadJobs()
Примечание. Важно вызвать метод loadJobs после предыдущих шагов (присоединение подписчика и имитация ответа сети);  в противном случае наш viewState издатель не будет выдавать никакого значения, потому что к этому моменту еще не был подключен ни один подписчик (издатели Combine не выдают никакого значения, если к нему не подключены подписчики).
Чтобы убедиться, что мы получаем правильное состояние, мы будем использовать XCTestExpectation — класс, который Apple предоставляет нам для создания асинхронных тестов.
Окончательный тестовый класс будет выглядеть так:
Вот и все. Вы можете найти полный пример кода в этом репозитории:
 
                                                                     
                                                                     
                                                                    