Комбинирование async/await и done

Jasmine предоставляет несколько способов тестирования асинхронного кода: через колбэк done, через промисы и с использованием async/await. Понимание того, как правильно комбинировать эти подходы, критично для стабильности и читаемости тестов.

В Jasmine асинхронный тест обычно определяется одной из двух стратегий:

  1. Использование колбэка done: Этот способ позволяет Jasmine понимать, когда асинхронная операция завершена. Тест не будет считаться завершённым, пока не вызовется функция done.
it('должен дождаться завершения асинхронной функции через done', (done) => {
    setTimeout(() => {
        expect(true).toBe(true);
        done();
    }, 100);
});
  1. Возврат промиса или использование async/await: Если функция теста объявлена как async, Jasmine автоматически ждёт выполнения промиса, возвращаемого этой функцией.
it('должен дождаться завершения асинхронной функции через async/await', async () => {
    const result = await Promise.resolve(42);
    expect(result).toBe(42);
});

Комбинирование async/await и done

На первый взгляд может показаться, что объединение async/await и done избыточно, но на практике это полезно при необходимости точного контроля над временем завершения теста или обработкой ошибок, которые могут возникнуть внутри async функции и которые не всегда корректно ловятся Jasmine без done.

it('должен корректно обрабатывать ошибки в async/await с done', (done) => {
    (async () => {
        try {
            const result = await Promise.reject(new Error('Ошибка'));
            // Этот код не будет выполнен
            expect(result).toBeDefined();
            done.fail('Ошибка не должна быть проброшена сюда');
        } catch (err) {
            expect(err.message).toBe('Ошибка');
            done();
        }
    })();
});

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

  • done вызывается только один раз; повторный вызов приведёт к ошибке Jasmine.
  • Внутри async функции можно использовать try/catch для обработки ошибок, а затем вызвать done() или done.fail(error).
  • Если забыть вызвать done() после завершения асинхронной операции, тест зависнет до таймаута.

Использование таймаутов

Асинхронные тесты часто требуют увеличения времени ожидания. Jasmine по умолчанию использует 5 секунд. Таймаут можно изменить через третий аргумент it:

it('должен дождаться медленной асинхронной функции', async (done) => {
    await new Promise(resolve => setTimeout(resolve, 7000));
    expect(true).toBe(true);
    done();
}, 10000);

Практические рекомендации

  1. Если асинхронная функция возвращает промис, предпочтительно использовать async/await без done. Это упрощает код и делает тесты более читаемыми.
  2. Использование done оправдано при работе с callback-ориентированными библиотеками или когда требуется точное управление завершением теста.
  3. Для обработки ошибок внутри async функций при комбинировании с done всегда использовать try/catch. Это предотвращает “зависание” теста при выбросе исключения.
  4. При работе с несколькими асинхронными вызовами можно комбинировать Promise.all с async/await для параллельного выполнения и затем вызывать done().
it('должен дождаться нескольких асинхронных операций', (done) => {
    (async () => {
        try {
            const results = await Promise.all([
                Promise.resolve(1),
                Promise.resolve(2),
                Promise.resolve(3)
            ]);
            expect(results).toEqual([1, 2, 3]);
            done();
        } catch (err) {
            done.fail(err);
        }
    })();
});

Советы по отладке

  • Если тест зависает, проверить, вызывается ли done().
  • Для ошибок в async/await всегда использовать try/catch или done.fail(error); иначе Jasmine не сможет корректно зафиксировать падение теста.
  • Избегать смешивания done и return промиса без необходимости — это может вызвать непредсказуемое поведение.

Комбинирование async/await и done предоставляет полный контроль над асинхронными тестами, позволяя одновременно использовать преимущества промисов и точного управления завершением теста через колбэк. Это особенно важно при сложных сценариях с множественными асинхронными зависимостями и обработкой ошибок.