基本测试
测试翻译功能
import i18next from 'i18next';
describe('i18next translations', () => {
beforeEach(() => {
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
welcome: 'Welcome',
greeting: 'Hello {{name}}'
}
}
}
});
});
test('should translate simple key', () => {
expect(i18next.t('welcome')).toBe('Welcome');
});
test('should translate with interpolation', () => {
expect(i18next.t('greeting', { name: 'John' })).toBe('Hello John');
});
});
React 组件测试
测试使用 useTranslation 的组件
import { render, screen } from '@testing-library/react';
import { useTranslation } from 'react-i18next';
import { I18nextProvider } from 'react-i18next';
import i18next from 'i18next';
function Welcome() {
const { t } = useTranslation();
return <h1>{t('welcome')}</h1>;
}
describe('Welcome component', () => {
const renderWithI18n = (component) => {
return render(
<I18nextProvider i18n={i18next}>
{component}
</I18nextProvider>
);
};
beforeEach(() => {
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
welcome: 'Welcome'
}
}
}
});
});
test('should display welcome message', () => {
renderWithI18n(<Welcome />);
expect(screen.getByText('Welcome')).toBeInTheDocument();
});
});
测试语言切换
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { useTranslation } from 'react-i18next';
import { I18nextProvider } from 'react-i18next';
import i18next from 'i18next';
function LanguageSwitcher() {
const { t, i18n } = useTranslation();
return (
<div>
<p>{t('currentLang')}</p>
<button onClick={() => i18n.changeLanguage('zh')}>中文</button>
<button onClick={() => i18n.changeLanguage('en')}>English</button>
</div>
);
}
describe('LanguageSwitcher', () => {
beforeEach(() => {
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
currentLang: 'English'
}
},
zh: {
translation: {
currentLang: '中文'
}
}
}
});
});
test('should switch language', async () => {
render(
<I18nextProvider i18n={i18next}>
<LanguageSwitcher />
</I18nextProvider>
);
expect(screen.getByText('English')).toBeInTheDocument();
fireEvent.click(screen.getByText('中文'));
await waitFor(() => {
expect(screen.getByText('中文')).toBeInTheDocument();
});
});
});
测试 Trans 组件
import { render, screen } from '@testing-library/react';
import { Trans } from 'react-i18next';
import { I18nextProvider } from 'react-i18next';
import i18next from 'i18next';
function MyComponent() {
return (
<Trans i18nKey="user.greeting">
Welcome <strong>{{name}}</strong>
</Trans>
);
}
describe('Trans component', () => {
beforeEach(() => {
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
'user.greeting': 'Welcome <1>{{name}}</1>'
}
}
}
});
});
test('should render translated content with interpolation', () => {
render(
<I18nextProvider i18n={i18next}>
<MyComponent />
</I18nextProvider>
);
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
});
});
Mock i18next
创建测试工具函数
// test-utils/i18n.js
import { initReactI18next } from 'react-i18next';
import i18next from 'i18next';
export const createI18nInstance = (resources = {}) => {
const instance = i18next.createInstance();
instance.use(initReactI18next).init({
lng: 'en',
resources: {
en: {
translation: resources
}
}
});
return instance;
};
export const renderWithI18n = (component, resources = {}) => {
const i18n = createI18nInstance(resources);
return render(
<I18nextProvider i18n={i18n}>
{component}
</I18nextProvider>
);
};
使用测试工具
import { renderWithI18n } from './test-utils/i18n';
test('should render with mocked translations', () => {
renderWithI18n(<MyComponent />, {
welcome: 'Welcome',
goodbye: 'Goodbye'
});
expect(screen.getByText('Welcome')).toBeInTheDocument();
});
测试延迟加载
import { render, screen, waitFor } from '@testing-library/react';
import { useTranslation } from 'react-i18next';
import { I18nextProvider } from 'react-i18next';
import i18next from 'i18next';
function LazyComponent() {
const { t, ready } = useTranslation('lazy', { useSuspense: false });
if (!ready) {
return <div>Loading...</div>;
}
return <p>{t('content')}</p>;
}
describe('Lazy loading', () => {
beforeEach(() => {
i18next.init({
lng: 'en',
resources: {
en: {
translation: {}
}
}
});
});
test('should show loading state initially', () => {
render(
<I18nextProvider i18n={i18next}>
<LazyComponent />
</I18nextProvider>
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
test('should load namespace and show content', async () => {
render(
<I18nextProvider i18n={i18next}>
<LazyComponent />
</I18nextProvider>
);
i18next.addResourceBundle('en', 'lazy', { content: 'Loaded content' });
await waitFor(() => {
expect(screen.getByText('Loaded content')).toBeInTheDocument();
});
});
});
测试缺失翻译
describe('Missing translations', () => {
beforeEach(() => {
i18next.init({
lng: 'en',
fallbackLng: 'en',
saveMissing: true,
missingKeyHandler: (lng, ns, key) => {
console.warn(`Missing translation: ${lng}.${ns}.${key}`);
},
resources: {
en: {
translation: {
existing: 'Existing translation'
}
}
}
});
});
test('should return key when translation is missing', () => {
const result = i18next.t('nonexistent');
expect(result).toBe('nonexistent');
});
test('should use fallback translation', () => {
i18next.addResourceBundle('en', 'translation', { fallback: 'Fallback' });
const result = i18next.t('fallback');
expect(result).toBe('Fallback');
});
});
集成测试
describe('i18next integration tests', () => {
test('should handle language change across components', async () => {
const { rerender } = render(
<I18nextProvider i18n={i18next}>
<App />
</I18nextProvider>
);
expect(screen.getByText('English')).toBeInTheDocument();
await i18next.changeLanguage('zh');
rerender(
<I18nextProvider i18n={i18next}>
<App />
</I18nextProvider>
);
expect(screen.getByText('中文')).toBeInTheDocument();
});
});
最佳实践
- 隔离测试: 每个测试应该独立运行,不依赖其他测试
- Mock 资源: 使用 mock 的翻译资源,避免依赖实际文件
- 测试工具: 创建可复用的测试工具函数
- 异步测试: 正确处理异步操作,如语言切换和延迟加载
- 覆盖边界: 测试缺失翻译、错误情况等边界条件
- 快照测试: 对翻译组件使用快照测试确保一致性
- 性能测试: 测试大量翻译时的性能表现