Без фиксации текущего времени тесты с timeago.js недетерминированы:
результат format(date) зависит от того, когда запускается
тест. Моки времени решают эту проблему.
it('показывает "2 дня назад"', () => {
const date = new Date(Date.now() - 2 * 86400 * 1000);
expect(format(date, 'ru')).toBe('2 дня назад'); // Нестабильно!
});
На следующий день Date.now() сместится, и вычисленная
дата станет “3 дня назад”. Тест упадёт без причины.
const FIXED_TIME = new Date('2025-06-01T12:00:00.000Z').getTime();
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(FIXED_TIME);
});
afterEach(() => {
jest.useRealTimers();
});
it('показывает относительную дату', () => {
const twoDaysAgo = new Date(FIXED_TIME - 2 * 86400_000);
expect(format(twoDaysAgo, 'ru')).toBe('2 дня назад');
});
import { vi, beforeEach, afterEach } from 'vitest';
import { format } from 'timeago.js';
const FIXED = new Date('2025-06-01T12:00:00Z');
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(FIXED);
});
afterEach(() => {
vi.useRealTimers();
});
it('форматирует корректно при фиксированном времени', () => {
const date = new Date(FIXED.getTime() - 3600_000); // 1 час назад
expect(format(date, 'ru')).toMatch(/час/);
});
Для сред без встроенной поддержки fake timers:
const OriginalDate = Date;
const FIXED = 1748779200000; // 2025-06-01T12:00:00Z
beforeAll(() => {
global.Date = class extends OriginalDate {
constructor(...args: any[]) {
if (args.length === 0) {
super(FIXED);
} else {
super(...args);
}
}
static now() { return FIXED; }
} as DateConstructor;
});
afterAll(() => {
global.Date = OriginalDate;
});
Когда нужно изолировать тест от реализации timeago.js:
jest.mock('timeago.js', () => ({
format: jest.fn(() => '2 часа назад'),
render: jest.fn(),
cancel: jest.fn(),
register: jest.fn(),
}));
import { format } from 'timeago.js';
it('компонент использует format', () => {
render(<TimeAgo date={new Date()} />);
expect(format).toHaveBeenCalledTimes(1);
});
jest.mock('timeago.js', () => ({
...jest.requireActual('timeago.js'),
format: jest.fn(() => '5 минут назад'),
}));
Остальные функции (render, cancel,
register) работают по-настоящему, только
format замокана.
const mockFormat = jest.fn();
jest.mock('timeago.js', () => ({ format: mockFormat }));
describe('с разными возвращаемыми значениями', () => {
it('отображает первое значение', () => {
mockFormat.mockReturnValue('только что');
render(<TimeAgo date={new Date()} />);
expect(screen.getByText('только что')).toBeInTheDocument();
});
it('обновляется при смене значения', () => {
mockFormat
.mockReturnValueOnce('только что')
.mockReturnValueOnce('1 минуту назад');
// Первый рендер — 'только что'
// После обновления — '1 минуту назад'
});
});
import { format } from 'timeago.js';
const FIXED_NOW = new Date('2025-06-01T12:00:00Z');
it('format с relativeDate', () => {
const result = format('2025-06-01T11:00:00Z', 'ru', {
relativeDate: FIXED_NOW,
});
expect(result).toMatch(/час/);
});
relativeDate — встроенная опция timeago.js. Позволяет
указать точку отсчёта явно, без подмены Date.now.
// test/factories/dates.ts
const BASE = new Date('2025-06-01T12:00:00Z');
export const dates = {
now: () => new Date(BASE),
minutesAgo: (n: number) => new Date(BASE.getTime() - n * 60_000),
hoursAgo: (n: number) => new Date(BASE.getTime() - n * 3600_000),
daysAgo: (n: number) => new Date(BASE.getTime() - n * 86400_000),
minutesLater: (n: number) => new Date(BASE.getTime() + n * 60_000),
};
// Использование в тестах
it('показывает "5 минут назад"', () => {
jest.setSystemTime(BASE);
expect(format(dates.minutesAgo(5), 'ru')).toMatch(/минут/);
});
it('хук обновляет значение по интервалу', () => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2025-06-01T12:00:00Z'));
const { result } = renderHook(() =>
useTimeAgo(new Date('2025-06-01T11:59:00Z')) // 1 минуту назад
);
expect(result.current).toMatch(/минут/);
// Перемотать на 2 минуты вперёд
act(() => { jest.advanceTimersByTime(120_000); });
// Значение должно остаться корректным
expect(typeof result.current).toBe('string');
jest.useRealTimers();
});
| Сценарий | Подход |
|---|---|
| Тест значения format | jest.setSystemTime + реальный format |
| Тест компонента без зависимости от библиотеки | Полный мок timeago.js |
| Тест поведения с разными выводами | mockReturnValueOnce |
| Тест с явной точкой отсчёта | relativeDate в FormatOptions |
| Тест таймеров и интервалов | jest.useFakeTimers +
advanceTimersByTime |