我如何开玩笑地模拟 react-i18next 和 i18n.js?

Posted

技术标签:

【中文标题】我如何开玩笑地模拟 react-i18next 和 i18n.js?【英文标题】:How do I mock react-i18next and i18n.js in jest? 【发布时间】:2017-12-14 16:58:22 【问题描述】:

package.json

"moduleNameMapper": 
  "i18next": "<rootDir>/__mocks__/i18nextMock.js"

i18n.js

import i18n from 'i18next'
import XHR from 'i18next-xhr-backend'
// import Cache from 'i18next-localstorage-cache'
import LanguageDetector from 'i18next-browser-languagedetector'

i18n
  .use(XHR)
  // .use(Cache)
  .use(LanguageDetector)
  .init(
    fallbackLng: 'en',
    // wait: true, // globally set to wait for loaded translations in translate hoc
    lowerCaseLng: true,
    load: 'languageOnly',
    // have a common namespace used around the full app
    ns: ['common'],
    defaultNS: 'common',
    debug: true,

    // cache: 
    //   enabled: true
    // ,

    interpolation: 
      escapeValue: false, // not needed for react!!
      formatSeparator: ',',
      format: function (value, format, lng) 
        if (format === 'uppercase') return value.toUpperCase()
        return value
      
    
  )

export default i18n

i18nextMock.js

/* global jest */
const i18next = jest.genMockFromModule('react-i18next')
i18next.t = (i) => i
i18next.translate = (c) => (k) => k

module.exports = i18next

出于某种原因,开玩笑的单元测试没有得到组件。

这是一个单元测试:

import React from 'react'
import  Provider  from 'react-redux'
import  MemoryRouter  from 'react-router-dom'
import  mount  from 'enzyme'

import  storeFake  from 'Base/core/storeFake'
import Container from '../container'

describe('MyContainer (Container) ', () => 
  let Component;

  beforeEach(() => 
    const store = storeFake()

    const wrapper = mount(
      <MemoryRouter>
        <Provider store=store>
          <Container />
        </Provider>
      </MemoryRouter>
    )

    Component = wrapper.find(Container)
  );

  it('should render', () => 
    // Component is undefined here
    expect(Component.length).toBeTruthy()
  )
)

【问题讨论】:

【参考方案1】:

您不需要模拟 t 函数,只需要 translate 一个。对于第二个,参数的用法很混乱,而且你需要返回一个组件。

我能够让它在我的项目中运行。这是我的模拟文件和我的 Jest 配置

Jest 配置

"moduleNameMapper": 
    "react-i18next": "<rootDir>/__mocks__/reacti18nextMock.js"

模拟react-i18next的源代码

/* global jest */
import React from 'react'

const react_i18next = jest.genMockFromModule('react-i18next')

const translate = () => Component => props => <Component t=() => '' ...props />

react_i18next.translate = translate

module.exports = react_i18next

【讨论】:

如何在测试文件中使用??【参考方案2】:

我在玩笑测试中使用了 Atemu 的 anwser,但最终在模拟中得到以下一行:

module.exports = t: key => key;

还修改了 jest 配置,因为我从 'i18next' 导入了 't':

"moduleNameMapper": 
    "i18next": "<rootDir>/__mocks__/reacti18nextMock.js"

【讨论】:

【参考方案3】:

在我的例子中,使用带有 TypeScript 的 useTranslation 钩子,如下所示:

const reactI18Next: any = jest.createMockFromModule('react-i18next');

reactI18Next.useTranslation = () => 
  return 
    t: (str: string) => str,
    i18n: 
      changeLanguage: () => new Promise(() => ),
    ,
  ;
;

module.exports = reactI18Next;

export default ;

jest.config.ts:

const config: Config.InitialOptions = 
  verbose: true,
  moduleNameMapper: 
    'react-i18next': '<rootDir>/__mocks__/react-i18next.ts',
  ,
;

【讨论】:

我可以按照这个答案配置模拟文件。 如何在测试文件中使用??. 它会自动与此配置一起使用 @Nik 。您只需开始测试,除非您手动取消模拟,否则 ti18n 函数将被模拟【参考方案4】:

“按原样”返回密钥并不是最好的。我们使用英文文本作为键,最好“评估”我们传入的值(即 t('timePeriod left') 评估为:'5剩余天数')。在这种情况下,我创建了一个辅助函数来执行此操作。以下是所需的笑话和额外文件:

Jest 配置(即 jest.config.js):

  moduleNameMapper: 
    'react-i18next': '<rootDir>/src/tests/i18nextReactMocks.tsx',
    'i18next': '<rootDir>/src/tests/i18nextMocks.ts',
    // ...
  ,

i18nextMocks.ts:

function replaceBetween(startIndex: number, endIndex: number, original: string, insertion: string) 
  const result = original.substring(0, startIndex) + insertion + original.substring(endIndex);
  return result;


export function mockT(i18nKey: string, args?: any) 
  let key = i18nKey;

  while (key.includes('')) 
    const startIndex = key.indexOf('');
    const endIndex = key.indexOf('');

    const currentArg = key.substring(startIndex + 2, endIndex);
    const value = args[currentArg];

    key = replaceBetween(startIndex, endIndex + 2, key, value);
  

  return key;


const i18next: any = jest.createMockFromModule('i18next');
i18next.t = mockT;
i18next.language = 'en';
i18next.changeLanguage = (locale: string) => new Promise(() => );

export default i18next;

i18nextReactMocks.tsx:

import React from 'react';
import * as i18nextMocks from './i18nextMocks';

export const useTranslation = () => 
  return 
    t: i18nextMocks.mockT,
    i18n: 
      changeLanguage: () => new Promise(() => ),
    ,
  ;
;

export const Trans = ( children ) => <React.Fragment>children</React.Fragment>;

我会免费提供模拟单元测试:)

import * as i18nextMocks from './i18nextMocks';

describe('i18nextMocks', () => 
  describe('mockT', () => 
    it('should return correctly with no arguments', async () => 
      const testText = `The company's new IT initiative, code named Phoenix Project, is critical to the
        future of Parts Unlimited, but the project is massively over budget and very late. The CEO wants
        Bill to report directly to him and fix the mess in ninety days or else Bill's entire department
        will be outsourced.`;

      const translatedText = i18nextMocks.mockT(testText);

      expect(translatedText).toBe(testText);
    );

    test.each`
      testText                            | args                                          | expectedText
      $'fileName is invalid.'       | $ fileName: 'example_5.csv'               | $'example_5.csv is invalid.'
      $'fileName is.'             | $ fileName: '   '                         | $'    is.'
      $'number of total'        | $ number: 0, total: 999                   | $'0 of 999'
      $'There was an error:\nerror' | $ error: 'Failed'                         | $'There was an error:\nFailed'
      $'Click:lili2li_3'    | $ li: '', li2: 'https://', li_3: '!@#$%'  | $'Click:https://!@#$%'
      $'happy?y✔sadlaugh'  | $ happy: '?', sad: '?', laugh: '?'     | $'??y✔??'
    `('should return correctly while handling arguments in different scenarios', ( testText, args, expectedText ) => 
      const translatedText = i18nextMocks.mockT(testText, args);

      expect(translatedText).toBe(expectedText);
    );
  );

  describe('language', () => 
    it('should return language', async () => 
      const language = i18nextMocks.default.language;

      expect(language).toBe('en');
    );
  );
);

【讨论】:

以上是关于我如何开玩笑地模拟 react-i18next 和 i18n.js?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 react-i18next 正确等待翻译

在类组件中使用 react-i18next

在React 中使用 react-i18next 国际化

react-i18next 出现错误尝试导入错误

Rails + React-I18next:找不到语言环境/

React项目多语言国际化:react-i18next插件实现——本地数据篇