Локализация в JavaScript опирается на встроенный модуль Intl API, который предоставляет стандартизированные средства форматирования дат, чисел, валют, строк и сравнения текста с учётом языка и региона. При переходе к тестированию подобного кода возникает фундаментальная проблема: результат работы часто зависит от окружения, версии ICU, настроек локали и часового пояса.
Unit-тесты в этом контексте должны обеспечивать не только проверку корректности форматирования, но и устойчивость к вариативности среды выполнения.
Форматирование даты и времени с учётом локали и временной зоны.
const formatter = new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: '2-digit'
});
formatter.format(new Date('2025-01-15T10:00:00Z'));
Ключевая проблема тестирования — различия в часовом поясе CI-серверов и локальных машин.
Форматирование чисел, валют и процентов.
const nf = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
});
nf.format(1234.56);
Результат зависит от правил локали: разделители, порядок символов, пробелы.
Сравнение строк с учётом языка.
const collator = new Intl.Collator('sv-SE');
['z', 'a', 'ä'].sort(collator.compare);
Порядок сортировки может отличаться радикально между локалями.
Основная сложность тестирования Intl заключается в недетерминированности результата:
Прямое сравнение строк часто приводит к хрупким тестам.
Любая функция, использующая Intl, должна принимать локаль явно.
function formatPrice(value, locale) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'USD'
}).format(value);
}
Тестирование становится детерминированным:
test('format price in en-US', () => {
expect(formatPrice(1000, 'en-US')).toBe('$1,000.00');
});
Для DateTimeFormat критично фиксировать время.
const date = new Date('2024-06-01T12:00:00Z');
const formatter = new Intl.DateTimeFormat('en-GB', {
timeZone: 'UTC',
dateStyle: 'full'
});
formatter.format(date);
Без явного timeZone тест может стать нестабильным.
Стратегия заключается в принудительном указании UTC:
new Intl.DateTimeFormat('ru-RU', {
timeZone: 'UTC'
});
Это устраняет влияние локальной системы.
Snapshot-тесты часто применяются для Intl, но требуют строгого контроля.
test('date snapshot', () => {
const formatter = new Intl.DateTimeFormat('ru-RU', {
timeZone: 'UTC',
dateStyle: 'medium'
});
expect(formatter.format(new Date('2024-01-01T00:00:00Z'))).toMatchSnapshot();
});
Риски snapshot-подхода:
Иногда применяется полное или частичное мокирование Intl.
global.Intl.DateTimeFormat = jest.fn(() => ({
format: () => '01.01.2024'
}));
Проблема такого подхода — потеря смысла теста: проверяется не логика, а заглушка.
const original = Intl.DateTimeFormat;
beforeAll(() => {
Intl.DateTimeFormat = function (locale, options) {
const formatter = new original(locale, {
...options,
timeZone: 'UTC'
});
return formatter;
};
});
Такой подход сохраняет реальную работу ICU, но фиксирует среду.
test('supports ru-RU locale', () => {
const result = new Intl.NumberFormat('ru-RU').format(1000);
expect(result).toContain('1');
});
Но подобные проверки слабые: они не гарантируют строгий формат.
const cases = [
['en-US', '$1,000.00'],
['de-DE', '1.000,00 $'],
['ru-RU', '1 000,00 $']
];
test.each(cases)('currency formatting %s', (locale, expected) => {
const nf = new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'USD'
});
expect(nf.format(1000)).toBe(expected);
});
Такой подход позволяет контролировать специфику каждой локали.
Сортировка зависит от языковых правил, поэтому важно фиксировать locale.
test('sorts German umlauts correctly', () => {
const collator = new Intl.Collator('de-DE');
const input = ['a', 'ä', 'b'];
const result = input.sort(collator.compare);
expect(result).toEqual(['a', 'ä', 'b']);
});
Без Collator сортировка будет зависеть от Unicode-бинарного порядка и даст некорректный результат.
new Intl.DateTimeFormat('en-US').format(new Date());
Результат различается в зависимости от TZ.
Решение — фиксация:
process.env.TZ = 'UTC';
или явное указание:
timeZone: 'UTC'
В Jest:
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-01T00:00:00Z'));
Это обеспечивает стабильность всех Date-операций.
Intl API часто зависит от диапазонов значений.
test('formats large numbers', () => {
const nf = new Intl.NumberFormat('en-US');
expect(nf.format(1000000)).toBe('1,000,000');
});
test('formats percentages', () => {
const nf = new Intl.NumberFormat('en-US', {
style: 'percent'
});
expect(nf.format(0.25)).toBe('25%');
});
Node.js использует ICU-библиотеку, которая определяет:
Разные версии Node могут давать разные результаты одного и того же теста.
Практика, упрощающая тестирование — изоляция Intl в отдельный модуль.
export function createCurrencyFormatter(locale) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'USD'
});
}
Тестируется не Intl напрямую, а обёртка:
test('currency formatter', () => {
const format = createCurrencyFormatter('en-US');
expect(format.format(10)).toBe('$10.00');
});
В CI окружении важно:
TZ=UTCУстойчивые unit-тесты локализации опираются на три принципа:
Структура теста обычно включает: