Тестирование кода, использующего date-fns, почти всегда сталкивается
с одной фундаментальной проблемой: время является недетерминированным
источником данных. Любая функция, зависящая от Date.now()
или new Date(), может возвращать разные значения при каждом
запуске теста.
Это приводит к:
Ключевая задача тестирования date-fns-кода заключается в создании контролируемого времени выполнения, при котором результат вычислений всегда воспроизводим.
Современные тестовые фреймворки позволяют перехватывать системное время.
import { format } from 'date-fns';
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-01T00:00:00Z'));
});
afterEach(() => {
jest.useRealTimers();
});
test('format date should be stable', () => {
const result = format(new Date(), 'yyyy-MM-dd');
expect(result).toBe('2024-01-01');
});
Фиксация времени через jest.setSystemTime делает любые
вызовы new Date() предсказуемыми.
Аналогичный подход реализуется в Vitest:
import { vi } from 'vitest';
import { format } from 'date-fns';
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2024-06-10T12:00:00Z'));
});
afterEach(() => {
vi.useRealTimers();
});
test('format stable date output', () => {
expect(format(new Date(), 'dd.MM.yyyy')).toBe('10.06.2024');
});
Функции форматирования являются наиболее чувствительной частью date-fns, так как они зависят от локали, таймзоны и входного объекта Date.
import { format } from 'date-fns';
test('formats ISO date correctly', () => {
const date = new Date('2023-12-25T00:00:00Z');
expect(format(date, 'yyyy/MM/dd')).toBe('2023/12/25');
});
import { format } from 'date-fns';
import { ru } from 'date-fns/locale';
test('formats date with locale', () => {
const date = new Date('2023-12-25T00:00:00Z');
const result = format(date, 'PPP', { locale: ru });
expect(result).toContain('декабря');
});
При тестировании локалей важно избегать сравнения всей строки целиком, поскольку форматирование может меняться между версиями.
date-fns широко используется для вычислений над датами, и тесты здесь должны учитывать крайние случаи.
import { differenceInDays } from 'date-fns';
test('difference in days', () => {
const a = new Date('2024-01-10');
const b = new Date('2024-01-01');
expect(differenceInDays(a, b)).toBe(9);
});
import { addMonths } from 'date-fns';
test('add months correctly handles overflow', () => {
const date = new Date('2024-01-31');
const result = addMonths(date, 1);
expect(result.getMonth()).toBe(1);
});
Особое внимание требуется к случаям:
Граничные случаи являются источником большинства ошибок.
import { startOfDay } from 'date-fns';
test('startOfDay resets time correctly', () => {
const date = new Date('2024-05-10T18:45:00Z');
const result = startOfDay(date);
expect(result.toISOString()).toBe('2024-05-10T00:00:00.000Z');
});
import { endOfMonth } from 'date-fns';
test('endOfMonth returns last day', () => {
const date = new Date('2024-02-10');
const result = endOfMonth(date);
expect(result.getDate()).toBe(29); // 2024 leap year
});
date-fns сам по себе не управляет таймзонами глубоко, поэтому часто используется связка с дополнительными инструментами.
При тестировании важно различать:
import { format } from 'date-fns-tz';
test('timezone formatting', () => {
const date = new Date('2024-01-01T00:00:00Z');
const result = format(date, 'yyyy-MM-dd HH:mm', {
timeZone: 'Europe/Berlin'
});
expect(result).toBe('2024-01-01 01:00');
});
Такие тесты должны фиксировать входной UTC-временной маркер, иначе результаты становятся непредсказуемыми.
В сложных приложениях date-fns используется внутри бизнес-логики, где время может вызываться косвенно.
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2025-03-15T10:00:00Z'));
});
Это гарантирует:
Функции вроде roundToNearestMinutes требуют проверки
множества сценариев.
import { roundToNearestMinutes } from 'date-fns';
test('rounds correctly down', () => {
const date = new Date('2024-01-01T10:14:00Z');
const result = roundToNearestMinutes(date, { nearestTo: 15 });
expect(result.getMinutes()).toBe(15);
});
Особое внимание:
date-fns придерживается принципа immutable API, где входной объект Date не изменяется.
import { addDays } from 'date-fns';
test('does not mutate input date', () => {
const date = new Date('2024-01-01');
const copy = new Date(date);
addDays(date, 5);
expect(date).toEqual(copy);
});
Это критически важное свойство, которое должно проверяться в тестовых наборах при использовании функций преобразования.
В реальных системах функции date-fns редко используются изолированно. Чаще они комбинируются.
import { addDays, format, startOfDay } from 'date-fns';
test('complex date pipeline', () => {
const base = new Date('2024-01-01T12:30:00Z');
const result = format(
startOfDay(addDays(base, 3)),
'yyyy-MM-dd'
);
expect(result).toBe('2024-01-04');
});
Такие тесты проверяют не только корректность функций, но и устойчивость цепочек преобразований.
Регрессионное тестирование в контексте date-fns фокусируется на:
test('regression: leap year handling', () => {
const date = new Date('2020-02-29');
const result = addYears(date, 1);
expect(result.getMonth()).toBe(1);
expect(result.getDate()).toBe(28);
});
import { parseISO } from 'date-fns';
test('parse ISO string', () => {
const date = parseISO('2024-07-01T00:00:00Z');
expect(date.getUTCFullYear()).toBe(2024);
});
Парсинг должен проверяться особенно строго, поскольку ошибки часто проявляются только в специфических форматах входных данных.
Ключевое требование к тестам date-fns-кода — полная воспроизводимость:
Date.now()Эти принципы формируют основу стабильного тестирования любой логики, построенной на date-fns.