Тестирование rejected promises

Jasmine предоставляет удобные средства для тестирования асинхронного кода, включая промисы. Особое внимание требует проверка отклонённых (rejected) промисов, так как ошибки в асинхронных функциях могут не проявляться сразу и пропускаться стандартными синхронными тестами.

Основы rejected promises

Промис в JavaScript может находиться в трёх состояниях:

  • pending — ожидание выполнения,
  • fulfilled — успешно выполнен,
  • rejected — отклонён, произошла ошибка.

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

Использование done для асинхронных тестов

Jasmine позволяет явно сигнализировать завершение асинхронного теста через колбэк done:

it('должен отклонять промис с правильной ошибкой', function(done) {
    someAsyncFunction()
        .then(() => {
            // Если промис неожиданно выполнен, тест провалится
            fail('Промис должен быть отклонён');
            done();
        })
        .catch(error => {
            expect(error.message).toBe('Ошибка выполнения');
            done();
        });
});

Особенности подхода:

  • done должен вызываться в любом ветвлении, иначе Jasmine не завершит тест корректно.
  • Использование fail() позволяет явно провалить тест, если промис неожиданно был выполнен.

Проверка через async/await

С введением async/await код становится более читаемым. Для проверки отклонённых промисов применяют конструкцию try/catch:

it('должен отклонять промис с нужной ошибкой', async function() {
    try {
        await someAsyncFunction();
        fail('Промис должен быть отклонён');
    } catch (error) {
        expect(error.message).toBe('Ошибка выполнения');
    }
});

Преимущества подхода:

  • Лаконичность и читаемость кода.
  • Не требуется явно использовать done.

Matcher toThrowError и промисы

Для синхронных функций Jasmine предоставляет toThrowError, но для промисов этот матчер работает только в обёртках:

it('должен проверять ошибку промиса через matcher', async function() {
    const rejectedPromise = Promise.reject(new Error('Ошибка промиса'));

    await expectAsync(rejectedPromise).toBeRejectedWithError('Ошибка промиса');
});

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

  • expectAsync() используется для асинхронных проверок.
  • toBeRejectedWithError() проверяет точное совпадение сообщения ошибки.
  • Существует также toBeRejected() без аргументов для проверки факта отклонения без анализа сообщения.

Проверка типов и содержимого ошибки

Jasmine позволяет проверять не только сообщение ошибки, но и её тип:

it('должен отклонять промис экземпляром TypeError', async function() {
    const rejectedPromise = Promise.reject(new TypeError('Неверный тип'));

    await expectAsync(rejectedPromise).toBeRejectedWithError(TypeError, 'Неверный тип');
});

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

Комбинация fulfilled и rejected промисов

В реальных приложениях часто требуется протестировать сценарий, где один промис может быть выполнен, а другой — отклонён. Jasmine позволяет писать тесты, обрабатывая несколько промисов одновременно:

it('обработка нескольких промисов', async function() {
    const promises = [
        Promise.resolve(42),
        Promise.reject(new Error('Ошибка второго промиса'))
    ];

    await expectAsync(promises[0]).toBeResolvedTo(42);
    await expectAsync(promises[1]).toBeRejectedWithError('Ошибка второго промиса');
});

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

  • Каждое ожидание асинхронного результата делается через expectAsync.
  • Тесты остаются изолированными, даже если один промис отклоняется.

Порядок обработки rejected промисов

Важно учитывать, что необработанные отклонения (UnhandledPromiseRejection) могут приводить к предупреждениям в Node.js или браузере. Для Jasmine это значит:

  • Всегда использовать catch или expectAsync(...).toBeRejected() для проверки промиса.
  • Никогда не оставлять промис без обработки в тесте.

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

  1. Всегда проверять, что промис действительно отклонён, иначе тест может скрыть баг.
  2. Использовать async/await и expectAsync для более читабельного кода.
  3. Проверять тип ошибки и сообщение, если это критично для логики приложения.
  4. Комбинировать rejected и fulfilled промисы аккуратно, проверяя каждый отдельно.
  5. Не игнорировать необработанные отклонения, чтобы избежать ложноположительных результатов.

Тестирование rejected промисов в Jasmine требует внимательности к асинхронной природе кода и правильного выбора инструментов (done, async/await, expectAsync). При соблюдении этих принципов тесты становятся надёжными и точными, позволяя уверенно контролировать ошибки асинхронных функций.