Тестирование setTimeout и setInterval

В JavaScript функции setTimeout и setInterval используются для выполнения кода с задержкой или циклически через определённые интервалы времени. При тестировании с помощью Jasmine важно учитывать асинхронность этих вызовов, чтобы тесты оставались предсказуемыми и надежными.


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

Проблема асинхронности: простое использование setTimeout в тесте без обработки асинхронного поведения приведет к тому, что Jasmine завершит тест до выполнения задержки. Это вызывает ложные успехи или ошибки. Для корректного тестирования необходимо либо использовать асинхронные колбэки Jasmine (done), либо подменять таймеры на управляемые “фейковые таймеры” (jasmine.clock()).

Пример с done

describe('Асинхронный тест с setTimeout', function() {
  it('должен выполнить функцию через 100 мс', function(done) {
    let value = false;

    setTimeout(function() {
      value = true;
      expect(value).toBe(true);
      done();
    }, 100);
  });
});

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

  • Функция done передаётся Jasmine как аргумент теста.
  • expect вызывается внутри таймера.
  • Без done тест завершится до выполнения setTimeout, что приведет к ошибке.

Использование фейковых таймеров

Jasmine предоставляет возможность подменять стандартные таймеры с помощью jasmine.clock(). Это особенно удобно для ускорения тестов и точного контроля времени.

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

beforeEach(function() {
  jasmine.clock().install();
});

afterEach(function() {
  jasmine.clock().uninstall();
});

Обязательные шаги: установка перед каждым тестом и восстановление после теста для предотвращения конфликтов с другими тестами.


Тестирование setTimeout с jasmine.clock()

describe('Тестирование setTimeout с фейковым таймером', function() {
  beforeEach(function() {
    jasmine.clock().install();
  });

  afterEach(function() {
    jasmine.clock().uninstall();
  });

  it('должен изменить значение через 100 мс', function() {
    let value = false;

    setTimeout(function() {
      value = true;
    }, 100);

    expect(value).toBe(false); // До прогонки таймера

    jasmine.clock().tick(100); // Прогоняем время на 100 мс

    expect(value).toBe(true);  // После прогонки таймера
  });
});

Пояснения:

  • Метод jasmine.clock().tick(ms) перематывает “виртуальное время” на заданное количество миллисекунд.
  • Таймеры и интервал срабатывают синхронно при вызове tick.
  • Такой подход делает тесты быстрыми и детерминированными.

Тестирование setInterval с фейковыми таймерами

setInterval вызывает функцию многократно через заданный интервал. Для тестирования нужно симулировать прогон времени соответствующее количество раз.

describe('Тестирование setInterval', function() {
  beforeEach(function() {
    jasmine.clock().install();
  });

  afterEach(function() {
    jasmine.clock().uninstall();
  });

  it('должен вызывать функцию каждые 50 мс', function() {
    let counter = 0;

    setInterval(function() {
      counter++;
    }, 50);

    expect(counter).toBe(0);

    jasmine.clock().tick(50);
    expect(counter).toBe(1);

    jasmine.clock().tick(150);
    expect(counter).toBe(4); // 50 + 150 = 200 мс, 4 вызова
  });
});

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

  • tick() учитывает все интервалы, прошедшие за указанное время.
  • Интервалы суммируются, поэтому можно перематывать сразу на большое значение.
  • Такой метод исключает реальные задержки и делает тесты мгновенными.

Специальные возможности jasmine.clock()

  1. Подмена Date: jasmine.clock() позволяет управлять системным временем с помощью mockDate. Это удобно при тестировании кода, зависящего от текущей даты и времени.

    const baseDate = new Date(2026, 0, 1);
    jasmine.clock().mockDate(baseDate);
    
    expect(new Date().getTime()).toBe(baseDate.getTime());
  2. Комбинирование с setTimeout и setInterval: Можно одновременно тестировать и задержки, и циклические вызовы, контролируя порядок событий.


Ошибки при тестировании таймеров

  • Отсутствие done или tick: приводит к ложным результатам.
  • Неудалённый jasmine.clock(): может влиять на другие тесты.
  • Неправильный расчёт интервалов при setInterval: важно учитывать, что метод tick суммирует все интервалы, а не только один вызов.

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

  • Использовать done для единичных, простых таймеров, когда тест не требует ускорения времени.
  • Использовать jasmine.clock() для ускорения тестов, работы с множественными таймерами и детерминированного поведения.
  • Всегда устанавливать и удалять фейковые часы в beforeEach и afterEach.
  • Проверять промежуточные значения переменных до и после прогонки таймера, чтобы убедиться в правильном срабатывании.

Точное управление временем и таймерами позволяет создавать быстрые, предсказуемые и надежные тесты асинхронного кода в Jasmine, что является ключевым элементом профессиональной разработки на JavaScript.