Вопрос или проблема
Я внедряю фреймворк 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)
}
Дело в том:
- Мне нужно настроить printerControllerMock асинхронно, потому что это асинхронный вызов. (Есть ли другой способ это сделать?)
- Когда PrinterListView инициализируется, он использует .task{}, чтобы получить принтеры через функцию, смоделированную в printersViewModel.
Но похоже, что создание снимков происходит до того, как .task{} завершится.
Есть ли способ задержать это или, в идеале, дождаться завершения?
Ответ или решение
Для корректного тестирования SwiftUI представлений с использованием swift-snapshot-testing
, особенно в случае, когда у вас есть асинхронные задачи, такие как .task {}
, необходимо убедиться, что тесты ожидают завершения этих асинхронных операций перед захватом снимков. Вот как вы можете сделать это:
-
Используйте XCTestExpectation: Это позволит вам ждать, пока асинхронная операция завершится, прежде чем продолжить выполнение теста. Вы создаете
XCTestExpectation
, ожидаете, пока ваша задача вViewModel
завершится, и затем выполняете захват снимка. -
Мокирование вызовов: Мокирование компонент должно выполняться асинхронно, как вы уже делаете это в вашем тесте.
Вот пример, как можно переписать ваш тест с использованием 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:)
позволяет тесту "пауза" до тех пор, пока не будет выполнено условие или не истечет таймаут. Важно выбрать подходящий таймаут в зависимости от предполагаемого времени, за которое завершится ваша задача.
Такое решение позволит вам гарантировать, что ваше представление корректно отобразится после завершения асинхронной задачи, что в свою очередь позволит корректно захватить снимок.