Async/await в тестах

Jasmine предоставляет мощные средства для тестирования асинхронного кода в JavaScript. Современный подход основан на использовании async/await, который позволяет писать асинхронные тесты так же читаемо, как синхронный код, без необходимости вручную управлять колбэками или промисами через done().


Асинхронные функции в тестах

Любая функция, помеченная как async, автоматически возвращает промис. Это открывает возможность использовать await для ожидания результата асинхронной операции. В Jasmine асинхронный тест можно объявить следующим образом:

it('должен получить данные с сервера', async () => {
    const data = await fetchData();
    expect(data).toBeDefined();
    expect(data.length).toBeGreaterThan(0);
});

Ключевые моменты:

  • async перед функцией теста сообщает Jasmine, что она асинхронная.
  • await приостанавливает выполнение теста до завершения промиса.
  • Ошибки, выброшенные в async функции, автоматически приводят к провалу теста.

Асинхронные beforeEach и afterEach

Для подготовки или очистки состояния перед или после тестов также допустимо использовать async/await. Например, загрузка тестовых данных перед каждым тестом:

beforeEach(async () => {
    await initializeDatabase();
});

afterEach(async () => {
    await clearDatabase();
});

Особенности:

  • Jasmine корректно ожидает завершения асинхронных функций в beforeEach и afterEach.
  • Можно комбинировать синхронный и асинхронный код внутри одного блока.

Работа с промисами без await

Иногда удобнее возвращать промис напрямую без использования async/await:

it('должен завершиться после промиса', () => {
    return fetchData().then(data => {
        expect(data).toBeDefined();
    });
});

Однако использование async/await повышает читаемость, особенно при сложных цепочках асинхронных вызовов.


Асинхронные ожидания с expectAsync

Jasmine предоставляет специальный набор функций для асинхронных проверок через expectAsync. Это особенно полезно для проверки промисов на успешное выполнение или отклонение:

it('должен успешно завершить промис', async () => {
    await expectAsync(fetchData()).toBeResolved();
});

it('должен отклонить промис', async () => {
    await expectAsync(fetchWithError()).toBeRejected();
});

it('должен отклонить промис с конкретной ошибкой', async () => {
    await expectAsync(fetchWithError()).toBeRejectedWithError('Network Error');
});

Примечания:

  • expectAsync работает только с промисами или асинхронными функциями.
  • Методы toBeResolved, toBeRejected, toBeRejectedWithError позволяют писать компактные проверки без использования .then или .catch.

Тайм-ауты в асинхронных тестах

По умолчанию Jasmine ожидает завершения асинхронного теста в течение 5 секунд. Этот лимит можно изменить глобально или для конкретного теста:

jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; // глобально

it('долго выполняющийся тест', async () => {
    await longRunningTask();
}, 15000); // тайм-аут конкретного теста

Важно устанавливать тайм-ауты, превышающие время выполнения асинхронной операции, иначе тест может завершиться с ошибкой.


Комбинация синхронного и асинхронного кода

В одном it можно использовать как синхронные, так и асинхронные проверки:

it('сочетание sync и async проверок', async () => {
    const initialValue = getValueSync();
    expect(initialValue).toBe(10);

    const asyncResult = await fetchData();
    expect(asyncResult).toContain('success');
});

Это позволяет разделять тест на логические части, не создавая излишней сложности.


Ошибки и исключения

Асинхронные ошибки, выброшенные внутри async функции, автоматически регистрируются Jasmine. Нет необходимости оборачивать код в try/catch, если цель — провал теста при ошибке:

it('тест завершится с ошибкой при исключении', async () => {
    await asyncFunctionThatThrows();
    // Если функция выбросит исключение, тест провалится автоматически
});

Для тестирования конкретных ошибок применяется expectAsync(...).toBeRejectedWithError.


Рекомендации по организации асинхронных тестов

  1. Использовать async/await вместо done(), если есть возможность. Это делает код чище и предотвращает забытые вызовы done().
  2. Разделять подготовку данных (beforeEach) и проверку результатов (it). Асинхронная подготовка упрощает поддержку тестов.
  3. Использовать expectAsync для промисов. Это избавляет от цепочек .then/.catch и повышает читаемость.
  4. Контролировать тайм-ауты. Долгие асинхронные операции требуют явного увеличения лимита ожидания.
  5. Обрабатывать ошибки. Любое исключение внутри async функции автоматически приводит к провалу теста, что упрощает выявление проблем.

Асинхронные возможности Jasmine с async/await позволяют писать чистые, понятные и надежные тесты, которые легко поддерживать даже при сложной логике взаимодействия с сервером, базой данных или сторонними сервисами.