Snapshot тестирование в системах интернационализации основано на идее фиксации стабильного состояния переведённого UI или строки и последующего сравнения этого состояния при изменениях. В контексте i18next это означает проверку того, что ключи переводов, интерполяции, плюрализация и форматирование языка не изменяются неожиданно при рефакторинге кода или обновлении словарей.
Snapshot-тестирование фиксирует результат выполнения функции в виде сериализованной структуры (обычно строка или дерево компонентов). При следующем запуске тестов результат сравнивается с сохранённым эталоном.
В случае i18next тестируемыми объектами чаще всего становятся:
t(key)useTranslation или
<Trans />Ключевая особенность интернационализации заключается в том, что один и тот же код может давать разные выходные данные в зависимости от активного языка, namespace и контекста.
Перед snapshot-тестированием важно обеспечить детерминированное поведение i18next. Любая нестабильность приведёт к постоянным падениям тестов.
Типичная конфигурация:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
debug: false,
resources: {
en: {
translation: {
hello: 'Hello',
items: 'Item',
items_plural: 'Items'
}
}
},
interpolation: {
escapeValue: false
}
});
export default i18n;
Для тестовой среды важно:
lngНаиболее базовый сценарий — проверка функции t.
Пример с Jest:
import i18n from './i18n';
describe('i18next snapshots', () => {
it('translates simple key', () => {
const result = i18n.t('hello');
expect(result).toMatchSnapshot();
});
});
Снапшот будет содержать:
Hello
При изменении перевода тест начнёт сигнализировать о расхождении.
Snapshot-тесты становятся хрупкими при наличии:
Пример нестабильного случая:
i18n.t('welcome', { name: 'User' });
Если перевод:
{
"welcome": "Welcome {{name}}"
}
снапшот будет стабильным. Но если формат изменится:
{
"welcome": "Hi {{name}}, today is {{date}}"
}
тест начнёт падать из-за появления нового поля.
Интерполяция — одна из ключевых особенностей i18next. Она требует отдельного внимания при тестировании.
it('interpolates variables correctly', () => {
const result = i18n.t('welcome', { name: 'Alex' });
expect(result).toMatchSnapshot();
});
Важно учитывать:
i18next поддерживает plural rules, которые зависят от языка.
Пример конфигурации:
{
"item": "Item",
"item_plural": "Items"
}
Тестирование:
it('handles plural form (singular)', () => {
const result = i18n.t('item', { count: 1 });
expect(result).toMatchSnapshot();
});
it('handles plural form (plural)', () => {
const result = i18n.t('item', { count: 5 });
expect(result).toMatchSnapshot();
});
Особенности:
lngВ React-экосистеме snapshot-тестирование чаще применяется к
компонентам, использующим i18next через useTranslation.
Пример компонента:
import React from 'react';
import { useTranslation } from 'react-i18next';
export function Greeting() {
const { t } = useTranslation();
return <h1>{t('hello')}</h1>;
}
Тест:
import React from 'react';
import renderer from 'react-test-renderer';
import { Greeting } from './Greeting';
import './i18n';
it('renders translated greeting', () => {
const tree = renderer.create(<Greeting />).toJSON();
expect(tree).toMatchSnapshot();
});
Снапшот фиксирует уже не строку, а структуру React-дерева:
<h1>
Hello
</h1>
i18next позволяет переключать язык динамически, что напрямую влияет на результат snapshot-тестов.
it('renders in different language', () => {
i18n.changeLanguage('en');
const enResult = i18n.t('hello');
i18n.changeLanguage('ru');
const ruResult = i18n.t('hello');
expect(enResult).toMatchSnapshot();
expect(ruResult).toMatchSnapshot();
});
Проблема заключается в том, что порядок выполнения и асинхронность смены языка могут влиять на результат. Поэтому часто используется синхронная инициализация тестового экземпляра i18next.
Для стабильных snapshot-тестов создаётся отдельный экземпляр i18next:
import i18next from 'i18next';
export function createTestI18n() {
const instance = i18next.createInstance();
instance.init({
lng: 'en',
fallbackLng: 'en',
resources: {
en: {
translation: {
hello: 'Hello'
}
}
},
interpolation: {
escapeValue: false
}
});
return instance;
}
Тест:
it('isolated instance snapshot', () => {
const i18n = createTestI18n();
const result = i18n.t('hello');
expect(result).toMatchSnapshot();
});
Такой подход исключает влияние глобального состояния.
i18next активно использует namespaces для разделения переводов.
i18n.init({
ns: ['common', 'auth'],
defaultNS: 'common',
resources: {
common: {
translation: {
save: 'Save'
}
},
auth: {
translation: {
login: 'Login'
}
}
}
});
Тестирование:
it('uses namespace correctly', () => {
const result = i18n.t('login', { ns: 'auth' });
expect(result).toMatchSnapshot();
});
Особенность snapshot-тестирования в этом случае — необходимость фиксировать не только ключ, но и контекст namespace.
При использовании i18next совместно с форматированием
через Intl появляются дополнительные источники
нестабильности.
Пример:
i18n.t('price', { value: 1234.56 });
Если форматирование зависит от локали, snapshot может меняться:
Для стабилизации тестов используют:
1. Асинхронная загрузка переводов
Если используется backend загрузчик, snapshot может сниматься до завершения загрузки.
2. Глобальное состояние i18n
Изменение языка в одном тесте влияет на другие.
3. Нестабильные ресурсы
Переводы, подтягиваемые из API, делают snapshot непредсказуемым.
4. Динамические данные
Вставка времени, случайных чисел, пользовательских значений ломает стабильность.
Используются следующие подходы:
Snapshot-тестирование в i18next чаще всего применяется не как основной механизм проверки логики, а как механизм обнаружения неожиданных изменений в переводах:
Такой подход особенно полезен в больших проектах с множеством локализаций, где ручная проверка каждого языка невозможна.