Как мне ожидать async Task{} с помощью swift-snapshot-testing?

Вопрос или проблема

Я внедряю фреймворк swift-snapshot-testing в мое приложение на SWIFTUI. https://swiftpackageindex.com/pointfreeco/swift-snapshot-testing
https://github.com/pointfreeco/swift-snapshot-testing

Как мне убедиться, что создание снимков ожидает завершения асинхронных задач во Views?

У меня есть представление SwiftUI, которое загружает данные с помощью .task{}. Я смоделировал наблюдаемый ViewModel, но создание снимков происходит до того, как асинхронная .task{} завершится, и ViewModel инициирует повторное рендеринг представления.

Вот тест:

func testPrintersListViewSnapShot() {
        
        let mockedPrinterResult: [PrinterModel] = [
            PrinterModel.moc(status: .maintenance, id: 1)
        ]
        
        Task {
            given(await printerControllerMock.fetchPrinters()).willReturn(.success(mockedPrinterResult))
        }
        
        let printersViewModel = PrintersViewModel(printerController: printerControllerMock, errorHandler: errorHandlerMock)
        
        let sut = PrintersListView(printersViewModel: printersViewModel)
        
        assertSnapshot(of: sut, as: .image)
    }

Дело в том:

  1. Мне нужно настроить printerControllerMock асинхронно, потому что это асинхронный вызов. (Есть ли другой способ это сделать?)
  2. Когда PrinterListView инициализируется, он использует .task{}, чтобы получить принтеры через функцию, смоделированную в printersViewModel.

Но похоже, что создание снимков происходит до того, как .task{} завершится.

Есть ли способ задержать это или, в идеале, дождаться завершения?

Ответ или решение

Для корректного тестирования SwiftUI представлений с использованием swift-snapshot-testing, особенно в случае, когда у вас есть асинхронные задачи, такие как .task {}, необходимо убедиться, что тесты ожидают завершения этих асинхронных операций перед захватом снимков. Вот как вы можете сделать это:

  1. Используйте XCTestExpectation: Это позволит вам ждать, пока асинхронная операция завершится, прежде чем продолжить выполнение теста. Вы создаете XCTestExpectation, ожидаете, пока ваша задача в ViewModel завершится, и затем выполняете захват снимка.

  2. Мокирование вызовов: Мокирование компонент должно выполняться асинхронно, как вы уже делаете это в вашем тесте.

Вот пример, как можно переписать ваш тест с использованием XCTestExpectation:

import XCTest
import SwiftUI
import SnapshotTesting

class YourTests: XCTestCase {

    func testPrintersListViewSnapShot() {

        let expectation = self.expectation(description: "Fetch printers expectation")

        let mockedPrinterResult: [PrinterModel] = [
            PrinterModel.moc(status: .maintenance, id: 1)
        ]

        Task {
            given(await printerControllerMock.fetchPrinters()).willReturn(.success(mockedPrinterResult))
            expectation.fulfill() // Уведомляем, что загрузка завершена
        }

        let printersViewModel = PrintersViewModel(printerController: printerControllerMock, errorHandler: errorHandlerMock)

        let sut = PrintersListView(printersViewModel: printersViewModel)

        // Ожидаем завершения асинхронной задачи перед захватом снимка
        wait(for: [expectation], timeout: 5.0)

        assertSnapshot(of: sut, as: .image)
    }
}

Пояснения к коду:

  • Мы создаем XCTestExpectation, который говорит тесту подождать, пока задача завершится.
  • Внутри асинхронной задачи (Task) мы вызываем expectation.fulfill(), чтобы уведомить, что задача завершилась.
  • Метод wait(for:timeout:) позволяет тесту "пауза" до тех пор, пока не будет выполнено условие или не истечет таймаут. Важно выбрать подходящий таймаут в зависимости от предполагаемого времени, за которое завершится ваша задача.

Такое решение позволит вам гарантировать, что ваше представление корректно отобразится после завершения асинхронной задачи, что в свою очередь позволит корректно захватить снимок.

Оцените материал
Добавить комментарий

Капча загружается...