How can I enable react-i18n translation file to be used in the unit tests done with react-testing-library and jest?
Asked Answered
O

3

29

I am making unit tests with jest and react-testing-library for my frontend application which is done with React. My unit tests worked nicely before I added the internationalization with react-i18next -library. Now when I run the tests, it seems that it doesn't find/use the translation files and all places where there should read something, are left empty. I'm using the newest react version with hooks and instead of React.Component I am using this kind of "const-components":

    const ComponentName = ({t}) => {
        return(
          <p>{t('example')}</p>
        )}
      export default ComponentName;

The internationalization works perfectly in the actual page but just that the unit tests fail due to not using the translation-file so I think the problem is with correctly mocking the translation files. I am only finding some suggestion solutions for the older react using this.variableName -type of solutions, which however doesn't help me much.

I have tried to mock it with jest.fn(), but I am not sure which function is the one, which I should mock and how to utilize the useTranslation() -function correctly from the tests.

    import React from 'react';
    import { useTranslation, Trans } from 'react-i18next';
    import { render } from '@testing-library/react';
    import ComponentName from './ComponentName';

    import '../locales/i18n';

    test('renders all documents in the list', () => {
      const mockUseTranslation = jest.fn();

      const { t, i18n } = mockUseTranslation();

      // const t = jest.fn();
      const c = render(<ComponentName t={t} />);
      expect(c.getByText('Translation File Title')).toBeDefined();
      expect(
        c.getAllByText(
          'Lorem ipsum'
        ).length
      ).toBe(3);
    });

Error message: Unable to find an element with the text: Translation File Title. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

So in short: the place, which should contain certain text is now totally empty.

Obie answered 22/7, 2019 at 14:22 Comment(3)
I don't think you should mock the i18n library. If you use debug what do you see in the page? Is the text empty? Does the i18n library use a provider to work?Levitus
react.i18next.com/misc/…Dorisdorisa
I'm not using redux, provider or store in my app so this doesnt really help me. Also those tests are with enzyme and I'm using react-testing-library. If I use the debug() tool it looks empty (in the place, which there should be translated text) as I mentioned before :/ So somehow the translation should be mocked or brought into the test.Obie
O
11

Eventually I got the mock working like this (in App.js):

jest.mock('react-i18next', () => ({
  useTranslation: () => ({
    t: key => key,
    i18n: { changeLanguage: jest.fn() }
  })
}));

In case somebody needs this.

Additionally inside components I was just using t={key=>key}, which enabled queries like this: expect(c.getByText('json.field.in.translation')).toBeDefined();

Obie answered 26/3, 2020 at 7:46 Comment(2)
you can not test values like {{count}} books with this approachStrongwilled
Do you an example posted somewhere by any chance?Hebbe
M
29

You should not mock the translation, instead render the component with translation library as Higher Order Component, for example;

import React from 'react';
import i18n from '../../../i18n' // your i18n config file
import { render } from '@testing-library/react';
import ComponentName from './ComponentName';
import { I18nextProvider } from 'react-i18next'

test('renders all documents in the list', () => {
    const c = render(
      <I18nextProvider i18n={i18n}> // actually give translation to your component
         <ComponentName />
      </I18nextProvider>
    );
    // example if you have a key called example
    expect(c.getByText(i18n.getDataByLanguage('en').translation.example)).toBeDefined(); 
});

Instead of calling your translation texts with i18n.getDataByLanguage('en') , you can give the default translation of your project, if it is French call it by i18n.getDataByLanguage('fr').

Also change your component like this, instead of taking useTranslation hook from props, take it inside the component with hooks

ComponentName.jsx

import { useTranslation } from 'react-i18next'

const ComponentName = () => {
  const { t } = useTranslation()

  return(
    <p>{t('example')}</p>
  )}

export default ComponentName;
Microclimatology answered 4/12, 2019 at 7:35 Comment(5)
I don't know what to say to you but thank you so much as I am sitting in front of this issue for the whole day <3 :) !Eweneck
I followed this and it works. For example I have another language to test for example Japanese. Should I create another i18n config so that it will render the Japanese strings and then check the string in the test file?Evangelista
This doesnt seem to work with more nested translations, how would I do it if I have something like this? i18n.getDataByLanguage('en').translation.example.exampleNested ?Maladjustment
this does not work with the current implementation - something is missing hereKicksorter
@Kicksorter I'm guessing as the library updates, its methods change and it does not work anymore, you need to adjust with the new implementation, I do not have access to the project anymore so not sureMicroclimatology
O
11

Eventually I got the mock working like this (in App.js):

jest.mock('react-i18next', () => ({
  useTranslation: () => ({
    t: key => key,
    i18n: { changeLanguage: jest.fn() }
  })
}));

In case somebody needs this.

Additionally inside components I was just using t={key=>key}, which enabled queries like this: expect(c.getByText('json.field.in.translation')).toBeDefined();

Obie answered 26/3, 2020 at 7:46 Comment(2)
you can not test values like {{count}} books with this approachStrongwilled
Do you an example posted somewhere by any chance?Hebbe
M
4

I did that this way:

  1. create the configuration for i18n in a separate file:

     const DEFAULT_LANGUAGE = "en";
     const DEFAULT_NAMESPACE = "translations";
     const enTranslationJson={ //bring that json from your real translation file!
      "nav": {
         "home": "Home",
         "example": "Example"
       },
      "page-title": "{{pageName}} Page",
     };
    
     i18n.use(initReactI18next).init({
      lng: DEFAULT_LANGUAGE,
      fallbackLng: DEFAULT_LANGUAGE,
      ns: [DEFAULT_NAMESPACE],
      defaultNS: DEFAULT_NAMESPACE,
      debug: false,
      interpolation: {
       escapeValue: false,
      },
      resources: { [DEFAULT_LANGUAGE]: { [DEFAULT_NAMESPACE]: 
       enTranslationJson } },
      });
    
      export default i18n;
    
  2. override render method with the I18nextProvider:

    import { render as rtlRender } from "@testing-library/react";
    import { I18nextProvider } from "react-i18next";
    
    const render = (ui: React.ReactElement) => {
       return rtlRender(<I18nextProvider i18n={i18n}>{ui}</I18nextProvider>);
    }
    
  3. In the tests I am using the render func and looking for the real translation values:

    test("home component render correctly", async () => {
      render(<Home />);
      const item = screen.getByText("Template Page");
      expect(item).toBeInTheDocument();
    });
    
Maharani answered 29/11, 2021 at 9:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.