Тестирование кода, использующего Intl API, требует учёта особенностей локализации, зависимости от ICU-данных и различий между средами выполнения. Основная сложность заключается в том, что результат работы большинства форматтеров зависит не только от входных данных, но и от окружения: языка, региональных настроек, версии ICU и даже платформы.
Одной из ключевых проблем является то, что результат форматирования не всегда стабилен между средами. Например, одна и та же дата может быть представлена разными строками в зависимости от локали и версии ICU:
Даже при фиксированной локали поведение может меняться при обновлении рантайма, что делает классическое сравнение строк в тестах ненадёжным.
Для тестируемости кода, использующего Intl, применяется несколько подходов:
Фиксация локали
Явное указание локали устраняет часть вариативности:
new Intl.DateTimeFormat("en-US").format(date);
Однако это не решает проблему различий ICU между версиями среды.
Фиксация параметров форматирования
Чем точнее задан формат, тем меньше вариативность результата:
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
});
Использование структурного тестирования вместо строк
Вместо сравнения итоговой строки тестируется структура:
formatToPartsПример:
const parts = new Intl.DateTimeFormat("en-US").formatToParts(date);
parts.find(p => p.type === "year").value;
Метод formatToParts является ключевым инструментом для
тестов, поскольку возвращает не строку, а структурированный
результат.
Пример разбиения числа:
const parts = new Intl.NumberFormat("de-DE").formatToParts(12345.67);
Результат содержит типизированные сегменты:
Тестирование через части позволяет игнорировать визуальные различия и проверять смысловые компоненты.
Для полного контроля над поведением применяется подмена Intl API.
Подходы:
1. Перезапись глобального Intl
global.Intl.NumberFormat = class {
constructor() {}
format(value) {
return `NUM(${value})`;
}
};
Используется для изоляции бизнес-логики от локализации.
2. Использование библиотек-стабов
Некоторые тестовые фреймворки позволяют автоматически стабилизировать Intl, фиксируя:
3. Инъекция форматтера
Вместо прямого использования Intl передаётся зависимость:
function formatPrice(value, formatter) {
return formatter.format(value);
}
Это позволяет в тестах подставлять детерминированные реализации.
Наиболее проблемная область — Intl.DateTimeFormat, так
как результат зависит от:
Фиксация временной зоны:
new Intl.DateTimeFormat("en-US", {
timeZone: "UTC"
});
В тестовой среде часто используется принудительная установка таймзоны:
process.env.TZ = "UTC";
Это уменьшает вариативность поведения и делает тесты воспроизводимыми.
Intl.NumberFormat относительно стабилен, но имеет
особенности:
Для тестирования важно учитывать не строку, а математический результат.
Пример проверки через parts:
const nf = new Intl.NumberFormat("fr-FR");
const parts = nf.formatToParts(-1234.5);
Проверяются:
Сортировка строк в разных языках различается существенно.
const collator = new Intl.Collator("de-DE");
В тестах нельзя полагаться на встроенный .sort() без
коллатора.
Корректный подход:
array.sort(collator.compare);
Проверка результата должна учитывать:
Snapshot-тесты плохо сочетаются с Intl API, потому что:
Типичная проблема:
Expected: "1,234.50"
Received: "1.234,50"
или изменение кавычек, пробелов, неразрывных символов.
Поэтому snapshot-подход требует фиксации:
Для детерминированных тестов применяются ограничения окружения:
en-US или en)Некоторые системы дополнительно используют Docker-образы с предустановленным ICU.
Intl API напрямую зависит от ICU-библиотеки.
Разные версии ICU могут менять:
Поэтому в больших проектах фиксируется версия ICU через:
Intl.Segmenter используется для разбиения текста на
слова, предложения или графемы.
const segmenter = new Intl.Segmenter("en", { granularity: "word" });
В тестах важно учитывать:
Проверка производится не по строкам, а по индексам сегментов.
На практике Intl-логика часто выносится в отдельный слой:
Это позволяет:
Часто проверяется не конкретный результат, а контракт:
Пример проверки контракта:
format() всегда возвращает stringformatToParts() всегда возвращает массив объектовcompare() всегда возвращает -1, 0 или 1Такой подход снижает зависимость от визуальных изменений форматирования.