React+i18next测试中无法加载public/locales翻译文件的问题
问题
我有一个基于React的项目,用i18next实现国际化,jest做测试。运行测试时,Backend插件尝试从public/locales文件夹加载翻译资源,控制台报错:
Error: Error: connect ECONNREFUSED 127.0.0.1:80 at Object.dispatchError
所有i18n功能测试都失败,比如i18n.exists(message)始终返回false。推测是测试阶段没有服务器监听,Backend的fetch请求失败导致翻译未加载。
项目配置如下:
i18n.js
import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; i18n .use(Backend) .use(LanguageDetector) .use(initReactI18next) .init({ debug: false, defaultNS: 'common', fallbackLng: ['es'], interpolation: { escapeValue: false, // not needed for react as it escapes by default }, ns: ['common', 'comments'], react: { useSuspense: false, }, }); export default i18n;
setupTests.js
import '@testing-library/jest-dom'; jest.mock('react-i18next', () => ({ ...jest.requireActual('react-i18next'), useTranslation: () => ({ ...jest.requireActual('react-i18next').useTranslation(), t: (key: string, interpolation: any) => `T_${key}${ interpolation ? `--${Object.keys(interpolation) ?.map((k) => `${k}:${interpolation[k]}`) .join('--')}` : '' }`, }), }));
我试过移除Backend插件,错误消失但生产环境无法加载翻译,测试还是失败;也试过手动导入JSON文件作为resources,测试和生产都正常,但不想手动维护多语言多命名空间的JSON文件。
想知道:
- 如何让Backend在测试中像生产环境一样加载
public/locales的翻译资源? - 或者如何用
fetch-mock-jest全局模拟对public/locales的请求?
解决方案
方法一:测试环境动态读取本地JSON文件替代Backend
通过环境判断,在测试时用Node.js的文件系统直接读取public/locales下的资源,不需要手动维护导入语句:
- 安装
fs-extra(可选,原生fs也可,fs-extra简化了文件操作):
npm install fs-extra --save-dev
- 修改
i18n.js,根据环境切换资源加载逻辑:
import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; import fs from 'fs-extra'; import path from 'path'; let resources = {}; // 测试环境下直接读取本地locales文件 if (process.env.NODE_ENV === 'test') { const localesDir = path.resolve(__dirname, '../public/locales'); const languages = fs.readdirSync(localesDir); languages.forEach(lang => { resources[lang] = {}; const nsFiles = fs.readdirSync(path.join(localesDir, lang)); nsFiles.forEach(file => { const ns = file.replace('.json', ''); resources[lang][ns] = fs.readJsonSync(path.join(localesDir, lang, file)); }); }); } i18n .use(process.env.NODE_ENV === 'test' ? null : Backend) // 测试环境禁用Backend .use(LanguageDetector) .use(initReactI18next) .init({ debug: false, defaultNS: 'common', fallbackLng: ['es'], interpolation: { escapeValue: false, }, ns: ['common', 'comments'], react: { useSuspense: false, }, resources: process.env.NODE_ENV === 'test' ? resources : undefined, // 测试环境注入本地资源 }); export default i18n;
jest默认会将NODE_ENV设置为test,无需额外配置。
方法二:用fetch-mock-jest全局模拟locales请求
全局拦截public/locales相关的fetch请求,返回本地JSON文件内容:
- 安装
fetch-mock-jest:
npm install fetch-mock-jest --save-dev
- 修改
setupTests.js,添加全局请求模拟:
import '@testing-library/jest-dom'; import fetchMock from 'fetch-mock-jest'; import fs from 'fs'; import path from 'path'; // 全局拦截locales的JSON请求 fetchMock.mock(/^\/locales\/.*\.json$/, (url) => { // 从URL中解析语言和命名空间 const urlSegments = url.split('/'); const lang = urlSegments[urlSegments.length - 2]; const ns = urlSegments[urlSegments.length - 1].replace('.json', ''); // 读取对应本地文件 const targetPath = path.resolve(__dirname, `../public/locales/${lang}/${ns}.json`); if (fs.existsSync(targetPath)) { return JSON.parse(fs.readFileSync(targetPath, 'utf8')); } // 文件不存在时返回空对象避免报错 return {}; }); jest.mock('react-i18next', () => ({ ...jest.requireActual('react-i18next'), useTranslation: () => ({ ...jest.requireActual('react-i18next').useTranslation(), t: (key: string, interpolation: any) => `T_${key}${ interpolation ? `--${Object.keys(interpolation) ?.map((k) => `${k}:${interpolation[k]}`) .join('--')}` : '' }`, }), }));
注意:如果测试环境使用jsdom,默认自带fetch实现,fetch-mock可以直接拦截请求;如果是其他环境,可能需要额外配置全局fetch。
内容的提问来源于stack exchange,提问作者Unapedra
相关产品推荐
相关产品推荐

