Какие инструменты и подходы используются для тестирования асинхронного кода в iOS?

«Какие инструменты и подходы используются для тестирования асинхронного кода в iOS?» — вопрос из категории Тестирование, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Основной инструмент: XCTest с XCTestExpectation Позволяет дождаться выполнения асинхронной операции.

func testAsyncNetworkCall() {
    // 1. Создаем expectation с описанием
    let expectation = XCTestExpectation(description: "Fetch user data")

    // 2. Запускаем асинхронный код
    networkService.fetchUser(id: "123") { result in
        // 3. Проверяем результат
        switch result {
        case .success(let user):
            XCTAssertEqual(user.name, "John")
        case .failure:
            XCTFail("Expected successful response")
        }
        // 4. Сигнализируем о завершении
        expectation.fulfill()
    }

    // 5. Ждем выполнения expectation с таймаутом
    wait(for: [expectation], timeout: 5.0)
}

Другие подходы и инструменты:

  • Тестирование Combine publishers: Использование XCTestExpectation с подпиской (sink) или специализированных операторов вроде collect() и first().

  • XCTWaiter для сложных сценариев: Позволяет ждать нескольких expectations и проверять их статус.

    let waiter = XCTWaiter()
    let result = waiter.wait(for: [exp1, exp2], timeout: 10)
    XCTAssertEqual(result, .completed) // Проверяем, что все expectations выполнены
  • Сторонние фреймворки:

    • Quick/Nimble: Предоставляют более выразительный синтаксис (expect(...).toEventually(equal(...))).
    • Mocking frameworks (Mockingbird, Cuckoo): Упрощают создание моков для асинхронных зависимостей.

Best Practices:

  1. Всегда устанавливайте разумный timeout.
  2. Не забывайте вызывать fulfill() во всех путях выполнения (успех и ошибка).
  3. Для тестов, зависящих от времени (таймеры, debounce), используйте моки часов (TestScheduler в Combine).