乐闻世界logo
搜索文章和话题

如何为 i18next 编写测试?

2月18日 22:07

基本测试

测试翻译功能

javascript
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 的组件

javascript
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(); }); });

测试语言切换

javascript
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 组件

javascript
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

创建测试工具函数

javascript
// 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> ); };

使用测试工具

javascript
import { renderWithI18n } from './test-utils/i18n'; test('should render with mocked translations', () => { renderWithI18n(<MyComponent />, { welcome: 'Welcome', goodbye: 'Goodbye' }); expect(screen.getByText('Welcome')).toBeInTheDocument(); });

测试延迟加载

javascript
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(); }); }); });

测试缺失翻译

javascript
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'); }); });

集成测试

javascript
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(); }); });

最佳实践

  1. 隔离测试: 每个测试应该独立运行,不依赖其他测试
  2. Mock 资源: 使用 mock 的翻译资源,避免依赖实际文件
  3. 测试工具: 创建可复用的测试工具函数
  4. 异步测试: 正确处理异步操作,如语言切换和延迟加载
  5. 覆盖边界: 测试缺失翻译、错误情况等边界条件
  6. 快照测试: 对翻译组件使用快照测试确保一致性
  7. 性能测试: 测试大量翻译时的性能表现
标签:i18next