Jasmine предоставляет мощные инструменты для тестирования
асинхронного кода. Одним из ключевых подходов является
управление временем с помощью фейковых таймеров. Этот
механизм позволяет контролировать выполнение setTimeout,
setInterval и других таймерных функций, ускоряя тесты и
делая их детерминированными.
Для работы с управляемыми таймерами используется функция
jasmine.clock(). Она предоставляет методы для установки и
контроля виртуального времени.
beforeEach(() => {
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
install() активирует фейковые таймеры.uninstall() возвращает стандартное поведение таймеров,
предотвращая побочные эффекты в других тестах.setTimeoutФейковые таймеры позволяют мгновенно продвигать время без реальной задержки. Например, асинхронная функция, которая выполняется через 1000 мс:
function delayedCallback(callback) {
setTimeout(() => {
callback('done');
}, 1000);
}
it('вызывает callback через 1 секунду', () => {
const callback = jasmine.createSpy('callback');
delayedCallback(callback);
expect(callback).not.toHaveBeenCalled();
jasmine.clock().tick(1000);
expect(callback).toHaveBeenCalledWith('done');
});
jasmine.createSpy() создаёт шпион для отслеживания
вызовов функции.tick(ms) — продвижение виртуального времени на
указанное количество миллисекунд.setIntervalАналогично, можно тестировать повторяющиеся таймеры:
let counter = 0;
beforeEach(() => {
jasmine.clock().install();
setInterval(() => {
counter++;
}, 500);
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('увеличивает счетчик каждые 500 мс', () => {
expect(counter).toBe(0);
jasmine.clock().tick(500);
expect(counter).toBe(1);
jasmine.clock().tick(1000);
expect(counter).toBe(3);
});
Каждое продвижение времени приводит к синхронному срабатыванию всех таймеров, упрощая проверку логики повторяющихся операций.
Date на
управляемуюJasmine позволяет также подменять объект
Date, что полезно для тестирования времени и
тайм-аутов, зависящих от текущей даты:
beforeEach(() => {
jasmine.clock().install();
jasmine.clock().mockDate(new Date(2026, 0, 11));
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('возвращает фиктивную текущую дату', () => {
const now = new Date();
expect(now.getFullYear()).toBe(2026);
expect(now.getMonth()).toBe(0);
expect(now.getDate()).toBe(11);
});
mockDate() заменяет стандартное поведение
Date, делая тесты независимыми от реального времени.Фейковые таймеры могут использоваться с промисами, если они зависят от таймеров. Пример с отложенным промисом:
function delayedPromise() {
return new Promise(resolve => {
setTimeout(() => resolve('resolved'), 2000);
});
}
it('резолвит промис через 2 секунды', async () => {
const promise = delayedPromise();
let result;
promise.then(value => result = value);
expect(result).toBeUndefined();
jasmine.clock().tick(2000);
await promise;
expect(result).toBe('resolved');
});
tick().await используется для корректного завершения промиса в
тесте.Часто встречаются ситуации, когда асинхронный код комбинирует
setTimeout и промисы:
function complexAsync(callback) {
setTimeout(() => {
Promise.resolve().then(() => callback('done'));
}, 1000);
}
it('вызывает callback после таймера и промиса', async () => {
const callback = jasmine.createSpy('callback');
complexAsync(callback);
expect(callback).not.toHaveBeenCalled();
jasmine.clock().tick(1000);
await Promise.resolve(); // даём промису выполниться
expect(callback).toHaveBeenCalledWith('done');
});
tick() запускает только таймеры.
Промисы внутри таймеров требуют отдельного ожидания через
await или цепочку then.uninstall() в
afterEach(), чтобы избежать конфликтов между тестами.tick() вместо реальных задержек, ускоряя
тесты и снижая нестабильность.tick().setTimeout и
setInterval, но и с библиотеками, использующими стандартные
таймеры, что делает их универсальным инструментом.Контроль времени с помощью Jasmine позволяет создавать детерминированные, быстрые и предсказуемые тесты для сложного асинхронного кода. Это особенно важно для функциональности, зависящей от задержек, интервалов и времени выполнения промисов.