如何在jest中模拟react-i18next和i18n.js?

27

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

由于某些原因,jest单元测试未能获取组件。

以下是一个单元测试:

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()
  })
})
5个回答

10

您不需要模拟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

6
如何在测试文件中使用它? - Nik

7

在我的案例中,使用 TypeScript 和 useTranslation hook,代码如下:

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',
  },
};

1
我能够通过遵循这个答案来配置模拟文件。 - Sunny Prakash
如何在测试文件中使用它? - Nik
2
它与此配置@Nik自动使用。您只需启动测试,除非手动取消模拟,否则ti18n函数将被模拟。 - Tirias

7
截至2023年,由于这个问题没有被接受的答案并且我必须略微修改react-i18next提供的示例,所以我发布以下内容,希望对某些人有所帮助。我正在使用jestreact-testing-library(RTL)。 如果你需要在不同的测试中使用不同的模拟数据,那么对模拟数据的需求就很少了,你可以在每个测试的开始处模拟该模块(例如,在一个测试中,你可能只需要模拟它,在另一个测试中,你想监视它的使用…)。然而,如果你要在多个测试中一次又一次地模拟它,最好按照其他答案建议单独创建模拟数据。

仅进行模拟

如果你只需要模拟该模块,以便测试无缝运行,react-i18next建议你执行以下操作:
jest.mock('react-i18next', () => ({
  // this mock makes sure any components using the translate hook can use it without a warning being shown
  useTranslation: () => {
    return {
      t: (str: string) => str,
      i18n: {
        changeLanguage: () => new Promise(() => {}),
        // You can include here any property your component may use
      },
    }
  },
}))

describe('Tests go here', () => {
   it('Whatever', () => {})
})

模拟和间谍

如果您正在使用useTranslation钩子并需要间谍其使用情况,那就是另一回事了。在react-i18next提供的示例中,他们使用的是enzyme,它没有跟上react的步伐,并且不能与RTL一起运行,以下是修复方法:

import { useTranslation } from 'react-i18next'

jest.mock('react-i18next', () => ({
  useTranslation: jest.fn(),
}))

const tSpy = jest.fn((str) => str)
const changeLanguageSpy = jest.fn((lng: string) => new Promise(() => {}))
const useTranslationSpy = useTranslation as jest.Mock

beforeEach(() => {
  jest.clearAllMocks()

  useTranslationSpy.mockReturnValue({
    t: tSpy,
    i18n: {
      changeLanguage: changeLanguageSpy,
      language: 'en',
    },
  })
})

describe('Tests go here', () => {
   it('Whatever', () => {})
})

唯一的变化是,每次测试之前必须设置模拟返回值,否则组件接收到的是undefined,随后您可以断言已调用t函数和changeLanguage函数。

7

我在我的Jest测试中使用了Atemu的答案,但最终在模拟中得到了以下一行代码:

module.exports = {t: key => key};

由于我从“i18next”导入“t”,因此还修改了jest配置:

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

0

直接返回键名“as is”并不是最好的做法。我们使用英文文本作为键名,希望能够“评估”我们传递的值(例如t('{{timePeriod}} left') 评估为:'剩余5天')。在这种情况下,我创建了一个辅助函数来实现这一点。以下是所需的jest和额外文件:

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:\n{{error}}'} | ${{ error: 'Failed' }}                        | ${'There was an error:\nFailed'}
      ${'Click:{{li}}{{li2}}{{li_3}}'}    | ${{ li: '', li2: 'https://', li_3: '!@#$%' }} | ${'Click:https://!@#$%'}
      ${'{{happy}}y✔{{sad}}{{laugh}}'}  | ${{ 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');
    });
  });
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接