Тестирование Promise

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


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

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

it('должен вернуть результат после завершения Promise', (done) => {
    const asyncFunction = () => Promise.resolve(42);

    asyncFunction().then(result => {
        expect(result).toBe(42);
        done();
    });
});

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

  • Функция done сообщает Jasmine, что тест завершен.
  • Если done() не вызван, тест будет считаться зависшим и закончится ошибкой по таймауту.
  • done.fail(error) позволяет корректно обработать ошибки асинхронного кода.
it('обрабатывает ошибки Promise', (done) => {
    const failingFunction = () => Promise.reject('Ошибка');

    failingFunction().catch(error => {
        expect(error).toBe('Ошибка');
        done();
    });
});

Возврат Promise из теста

Альтернативой использованию done является прямой возврат промиса из блока it. Jasmine дождется его завершения автоматически.

it('возвращает Promise с результатом', () => {
    const asyncFunction = () => Promise.resolve(100);

    return asyncFunction().then(result => {
        expect(result).toBe(100);
    });
});

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

  • Код становится чище, не требуется явно вызывать done.
  • Легче обрабатывать цепочки промисов.
  • Ошибки автоматически приводят к провалу теста.

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

Jasmine поддерживает асинхронные хуки. Если необходимо подготовить данные или очистить их перед/после теста, можно использовать done или возвращать Promise.

let data;

beforeEach(() => {
    return fetchData().then(response => {
        data = response;
    });
});

afterEach(() => {
    return clearData().then(() => {
        data = null;
    });
});

it('проверяет данные после fetch', () => {
    expect(data).toBeDefined();
});

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

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

Проверка асинхронных исключений

Для тестирования промисов, которые должны завершаться с ошибкой, важно корректно обрабатывать catch.

it('генерирует ошибку в Promise', () => {
    const failingFunction = () => Promise.reject(new Error('fail'));

    return failingFunction().catch(error => {
        expect(error.message).toBe('fail');
    });
});

Для промисов с async/await проверка ошибок может выполняться через конструкцию try/catch:

it('async/await: проверка ошибки', async () => {
    const failingFunction = () => Promise.reject(new Error('fail'));

    try {
        await failingFunction();
    } catch (error) {
        expect(error.message).toBe('fail');
    }
});

Использование async/await с Jasmine

Современный способ работы с асинхронным кодом — async/await. Jasmine полностью поддерживает async функции в тестах и хуках.

it('async/await: проверка результата Promise', async () => {
    const asyncFunction = () => Promise.resolve(7);
    const result = await asyncFunction();
    expect(result).toBe(7);
});

Преимущества async/await:

  • Код выглядит синхронным, упрощается чтение цепочек промисов.
  • Не требуется явно возвращать промис или вызывать done.
  • Исключения автоматически приводят к провалу теста.

Таймауты и асинхронные операции

По умолчанию Jasmine ожидает завершения асинхронного кода 5 секунд. Если промис выполняется дольше, тест упадет по таймауту. Время ожидания можно изменить:

it('длинная асинхронная операция', async () => {
    const longPromise = () => new Promise(resolve => setTimeout(() => resolve('ok'), 7000));
    const result = await longPromise();
    expect(result).toBe('ok');
}, 10000); // увеличенный таймаут до 10 секунд

Комбинирование нескольких промисов

Иногда необходимо дождаться нескольких промисов одновременно. В Jasmine это удобно делать через Promise.all.

it('ожидает несколько промисов', async () => {
    const p1 = Promise.resolve(1);
    const p2 = Promise.resolve(2);

    const results = await Promise.all([p1, p2]);
    expect(results).toEqual([1, 2]);
});

Использование Promise.allSettled позволяет проверять результаты независимо от того, завершились ли промисы успешно или с ошибкой.


Шпионы и асинхронные функции

Jasmine позволяет создавать шпионы (spyOn) для асинхронных методов. При работе с промисами можно возвращать промис через and.returnValue.

const api = {
    fetchData: () => Promise.resolve('data')
};

spyOn(api, 'fetchData').and.returnValue(Promise.resolve('mockData'));

it('шпион возвращает промис', async () => {
    const result = await api.fetchData();
    expect(result).toBe('mockData');
});

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

  • Шпионы позволяют контролировать асинхронные зависимости.
  • Можно использовать and.callFake для имитации асинхронного поведения с разными сценариями.

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

  1. Предпочтение отдавать async/await вместо done — код читается проще.
  2. Для тестов, где важно обработать ошибки, использовать конструкцию try/catch или .catch на промисах.
  3. При тестировании нескольких асинхронных операций — Promise.all или Promise.allSettled обеспечивают согласованное ожидание.
  4. Увеличивать таймаут для длительных операций.
  5. Использовать шпионы для контроля внешних зависимостей и возвращаемых промисов.

Эти методы позволяют строить стабильные и предсказуемые тесты асинхронного кода в Jasmine, обеспечивая корректную проверку результатов и обработку ошибок.