Использование параметра done

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

Основы работы с done

done — это функция, которую Jasmine предоставляет как аргумент в it или beforeEach/afterEach. Передача done в функцию теста сообщает Jasmine, что тест асинхронный, и завершение теста должно ожидать вызова этой функции.

Пример базового использования:

it('асинхронный тест с setTimeout', function(done) {
    let value = false;

    setTimeout(function() {
        value = true;
        expect(value).toBe(true);
        done(); // сигнализируем Jasmine о завершении теста
    }, 1000);
});

В этом примере тест не будет считаться завершенным, пока не будет вызван done(). Если done() не будет вызван, тест упадет с таймаутом, который по умолчанию равен 5 секунд.

Использование done с промисами

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

it('тест с промисом', function(done) {
    fetchDataFromServer()
        .then(function(data) {
            expect(data).toBeDefined();
            done();
        })
        .catch(function(error) {
            done.fail(error); // сообщает Jasmine о неуспешном завершении
        });
});

Метод done.fail() позволяет правильно обрабатывать ошибки асинхронного кода, предотвращая ложноположительные результаты тестов.

done в хуках beforeEach и afterEach

Параметр done можно использовать не только в тестах, но и в хуках, чтобы подготовка или очистка выполнялись асинхронно:

beforeEach(function(done) {
    initializeDatabase(function() {
        done();
    });
});

afterEach(function(done) {
    cleanupDatabase(function() {
        done();
    });
});

Это особенно важно при работе с базами данных, сетевыми сервисами и другими ресурсами, требующими асинхронной инициализации.

Важные нюансы и ошибки

  1. Двойной вызов done — если функция done() вызывается более одного раза, Jasmine выбрасывает ошибку. Это часто случается при использовании таймеров или промисов с несколькими ветвями.
  2. Пропуск done — если забыть вызвать done(), тест упадет по таймауту. Для уменьшения вероятности ошибки рекомендуется всегда использовать return с промисами, если они поддерживаются.
  3. Смешивание промисов и done — лучше использовать один подход. Если функция возвращает промис, можно обойтись без done. Пример корректного использования без done:
it('тест с возвращением промиса', function() {
    return fetchDataFromServer().then(function(data) {
        expect(data).toBeDefined();
    });
});

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

  • Использовать done() для любых колбэков, таймеров, событий или асинхронных функций, которые не возвращают промис.
  • Всегда обрабатывать ошибки асинхронного кода через done.fail(error), чтобы тест корректно падал при исключениях.
  • Проверять, что done() вызывается точно один раз и только после завершения всех асинхронных операций.
  • Для промисов или async/await предпочтительнее возвращать промис или использовать async функцию, избегая лишнего done.

Пример комплексного асинхронного теста

it('комбинированный асинхронный тест', function(done) {
    let counter = 0;

    setTimeout(function() {
        counter += 1;
        fetchDataFromServer().then(function(data) {
            counter += 1;
            expect(counter).toBe(2);
            expect(data).toBeDefined();
            done();
        }).catch(done.fail);
    }, 500);
});

В этом примере несколько асинхронных операций объединены: сначала таймер, затем промис. done() вызывается только после завершения всех шагов, что обеспечивает правильную последовательность проверок.

Использование done обеспечивает точный контроль над асинхронным тестированием в Jasmine и позволяет избегать ошибок, связанных с преждевременным завершением тестов.