React-i18next: How to test using Jest?

Created on 14 May 2018  路  20Comments  路  Source: i18next/react-i18next

I've followed the other examples here but I'm stumped with how to load react-18next into my Jest setup. I want to be able to verify that a component can render which is using translate.

Test File

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './i18n';

it('renders without crashing', () => {
     const div = document.createElement('div');
     ReactDOM.render(<App />, div);
});

Jest Errors

TypeError: Cannot read property 'type' of undefined

at I18n.use (node_modules/i18next/dist/commonjs/i18next.js:237:16)

Jest Configuration

import React, { Component } from 'react';

jest.mock('react-i18next', () => ({
     translate: () => Component => props => <Component t={() => ''} {...props} />
}));

i18n Configuration

What I _think_ is happening is that i18next-xhr-backend isn't working with Jest therefore my translation files aren't loading...but I'm not sure how to resolve this.

import i18n from 'i18next';
import Backend from 'i18next-xhr-backend';
import { reactI18nextModule } from 'react-i18next';

i18n
     .use(Backend)
     .use(reactI18nextModule)
     .init({
          interpolation: {
               // React already does escaping
               escapeValue: false
          },
          lng: 'en',
          fallbackLng: 'en',
          backend: {
               loadPath: './locales/{{lng}}/{{ns}}.json'
          },
          debug: true,
          react: {
               wait: true
          }
     });

export default i18n;

Most helpful comment

For anyone that comes here looking for an answer, here is an update now that I understand i18n, jest & enzyme a little better. In my example above shallow rendering only worked because it doesn't see anything inside of <I18n>.

_Note: I am using the render props method NamespacesConsumer vs. the hoc withNamespaces, IMHO it is much easier to comprehend and I don't understand why the hoc method is still the "recommended" way to use this library._

  1. mount is the way to go if you actually want to write some substantial tests.
  2. Create a separate i18n.js file used specifically for testing, in my case I use i18nTests.js
  3. Make sure you are updated to the latest version of react-i18next to take full advantage of NamespacesConsumer (replaced I18n component). Follow documentation on how to update your app for this.
  4. Here's what I couldn't figure out the first time around... When mounting the component, wrap it with <I18nextProvider> and be sure to pass in the prop i18n which you get from i18nTests.js. Because Enzyme is only mounting this component, it using your index.js which wraps the app with it.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';

import App from './App';
import i18n from './i18n';

ReactDOM.render(
  <I18nextProvider i18n={i18n}>
    <App />
  </I18nextProvider>,
  document.getElementById('app')
);

MyComponent.test.js

import React from 'react';
import { mount } from 'enzyme';

import { I18nextProvider, NamespacesConsumer } from 'react-i18next';
import i18n from './i18nTests';

import MyComponent from './MyComponent';

describe('MyComponent', () => {
  it('mounts without throwing an error', () => {
    const wrapper = mount(
      <I18nextProvider i18n={i18n}>
        <MyComponent />
      </I18nextProvider>
    );

    expect(wrapper.contains(
      <NamespacesConsumer>
        {(t) => (
          <div className="content-heading">
            {t('page_titles.MY_COMPONENT')}
          </div>
        )}
      </NamespacesConsumer>
    )).toBe(true);
  });
});

All 20 comments

i guess one of those imports is not working as expected -> Backend or reactI18nextModule is null -> please check that.

Beside that setting lng = cimode will help you to return keys instead of translations making testing more easy.

In the interest of narrowing down the issue I completely removed the backend code and I'm loading my translations via resources. I'm mocking react-i18next with the Jest configuration so I thought that would take care of it not loading however the line that is causing the error in node_modules/i18next/dist/commonjs/i18next.js is module.type...

Updated i18n-test.js

import i18n from 'i18next';
import translationEnglish from '../public/locales/en/translation.json';

i18n
    .init({
        fallbackLng: 'cimode',
        debug: false,
        saveMissing: false,
        interpolation: {
            escapeValue: false // React already does escaping
        },
        resources: {
            en: {
                translation: translationEnglish
            }
        },
        react: {
            wait: false,
            nsMode: 'fallback'
        }
    });

export default i18n;

like said:

TypeError: Cannot read property 'type' of undefined

at I18n.use (node_modules/i18next/dist/commonjs/i18next.js:237:16)

points to passing a undefined or null to: https://github.com/i18next/i18next/blob/master/src/i18next.js#L156

I understand that part, what I don't understand is why? I've followed all of the i18next docs and I'm still not passing the tests so how do I fix it?

I've made some headway by changing my codebase to utilize I18nextProvider instead.

Test File

import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';

import App from './App';
import i18n from './i18n-test';

it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(
        <I18nextProvider i18n={i18n}>
            <App />
        </I18nextProvider>,
        div
    );
});

However now Jest is complaining about <I18nextProvider> being undefined. My application works perfectly but the tests don't pass.

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

According to this Stack Overflow post it has to do with improperly importing/exporting the component.

so you will need to find out how to correctly import it for jest...that's the point. might be you need to import default (which webpack does automatically)...

so honestly not much i can do i guess.

Okay thanks

Spent all day trying to figure this out, there are only two ways to import a component, in this case I18nextProvider is a named component so the correct way is as I already have it import { I18nextProvider } from 'react-i18next';. If it was a default component then I would import it without the curly braces.

Jest follows the same conventions, which is why this works import React, { Component } from 'react'; so I'm at a loss as to why this isn't working. Is there anyone using Jest to test i18next??? I can't be the only person having this issue.

At the top of my Jest Configuration I now have

import { I18n, I18nextProvider } from 'react-i18next';

Which has somewhat progressed the error to...

Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

This leads me to believe that the issue is in how you're exporting the components from react-i18next and not how I'm importing them.

Nope....i guess your're using Trans component or other components in react-i18next -> means you need to mock those too:

https://github.com/i18next/react-i18next/tree/master/example/test-jest

https://github.com/i18next/react-i18next/blob/master/example/test-jest/__mocks__/react-i18next.js

Took me an hour to set this up...hopefully you consider using https://locize.com in future (for all that help you got ;))

guess this could be closed...if still got any issues beside using with jest...let me know.

Thank you for your help with this, settling on using Enzyme certainly helps and I was able to get it working with shallow rendering.

I will certainly be looking into your service when we are ready to start translating. As of right now I'm just trying to get the application up and running with some preliminary tests passing.

import React from 'react';
import { shallow } from 'enzyme';

import MyComponent from './MyComponent';
import { I18n } from 'react-i18next';

describe('MyComponent', function() {
  it('should render without throwing an error', function() {
    expect(
        shallow(<MyComponent />)
        .contains(
            <I18n>
                {(t) => (
                    <div className="content-heading">
                        {t('sidebar.nav.dashboard.MY_COMPONENT')}
                    </div>
                )}
             </I18n>
        )
    )
    .toBe(true);
  });
});

For anyone that comes here looking for an answer, here is an update now that I understand i18n, jest & enzyme a little better. In my example above shallow rendering only worked because it doesn't see anything inside of <I18n>.

_Note: I am using the render props method NamespacesConsumer vs. the hoc withNamespaces, IMHO it is much easier to comprehend and I don't understand why the hoc method is still the "recommended" way to use this library._

  1. mount is the way to go if you actually want to write some substantial tests.
  2. Create a separate i18n.js file used specifically for testing, in my case I use i18nTests.js
  3. Make sure you are updated to the latest version of react-i18next to take full advantage of NamespacesConsumer (replaced I18n component). Follow documentation on how to update your app for this.
  4. Here's what I couldn't figure out the first time around... When mounting the component, wrap it with <I18nextProvider> and be sure to pass in the prop i18n which you get from i18nTests.js. Because Enzyme is only mounting this component, it using your index.js which wraps the app with it.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';

import App from './App';
import i18n from './i18n';

ReactDOM.render(
  <I18nextProvider i18n={i18n}>
    <App />
  </I18nextProvider>,
  document.getElementById('app')
);

MyComponent.test.js

import React from 'react';
import { mount } from 'enzyme';

import { I18nextProvider, NamespacesConsumer } from 'react-i18next';
import i18n from './i18nTests';

import MyComponent from './MyComponent';

describe('MyComponent', () => {
  it('mounts without throwing an error', () => {
    const wrapper = mount(
      <I18nextProvider i18n={i18n}>
        <MyComponent />
      </I18nextProvider>
    );

    expect(wrapper.contains(
      <NamespacesConsumer>
        {(t) => (
          <div className="content-heading">
            {t('page_titles.MY_COMPONENT')}
          </div>
        )}
      </NamespacesConsumer>
    )).toBe(true);
  });
});

@CWSites Did you actually archieve to test a component wrapped with withNamespaces hoc ?? Can't find any documentation for this.

@Renaud009

https://github.com/i18next/react-i18next/blob/master/test/withTranslation.spec.js -> is v10 but principle stays the same

v10: https://github.com/i18next/react-i18next/tree/master/example/test-jest
v9: https://github.com/i18next/react-i18next/tree/master/example/v9.x.x/test-jest

@jamuhl Thank you I just figured this out, I'am currently using next-i18next which still uses v9 so I was a bit confused 馃憤

@Renaud009 yup I did :)

My solution for this:

jest.mock('react-i18next', () => {
  return {
    withTranslation: x => y => y,
    Trans: ({ children }) => children,
  };
});

I have two simple use cases where I have to use <Trans /> (and with typescript) in my code base, and these have worked for each of my two cases:

A simpler case:

jest.mock('react-i18next', () => ({
  useTranslation: (): {} => ({ t: (key: string): string => key }),
  Trans: (): ReactElement => <></>,
}));

A slightly more complicated case:

type TransObject = {
  i18nKey: string,
  components: ReactElement[],
  values: {}
}

jest.mock('react-i18next', () => ({
  useTranslation: (): {} => ({ t: (key: string): string => key }),
  Trans: (transElement: TransObject): ReactElement => (transElement.components[0])
}));

If you aren't using <Trans /> then you don't even need to mock that bit.

If you need the mock translate function to actually return translated values you could set it up like this:

import { translations as mockTranslations } from './path/to/translations';

// mockTranslations = { en: { helloWorld: 'Hello World!' } }

type UseTranslationHook = {
  t: (key: string) => string;
};

// Mock the "useTranslation" hook.
jest.mock('react-i18next', () => ({
  useTranslation: (): UseTranslationHook => ({
    t: (key) => mockTranslations.en[key],
  }),
}));

@KlavierCat Hi, i tried your 'simple case', which actually prevents <Trans> from rendering in tests. While it gets rid off of the issue, it does not render that element (just to point out). For 'slight complicated case' well, i get error that you can't access to the element [0] of the undefined. Am I missing something?

@SuperAL Hi, I tried with your mock like this,

const translation= JSON.stringify({
  "flow": {
    "page": {
      "text": "First paragraph<1/><2/>Second paragraph<4/><5/>Last paragraph",
    },
  ],
});
jest.mock('react-i18next', () => ({
  withTranslation: () => (Component: any) => {
    Component.defaultProps = {...Component.defaultProps, t: (translation: string) => translation};
    return Component;
  },
  Trans: ({children}) => children,
}));

and it resulted with this in the snapshot, which i guess is as expected.

<br />
<br />
text
<br />
<br />
text

My <Trans> usage looks like:

<Trans i18nKey="flow.page.text" ns="translation">
  text
  <br />
   <br />
   text
   <br />
   <br />
   text
</Trans>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dawsbot picture dawsbot  路  4Comments

simoami picture simoami  路  3Comments

pwiszowaty0 picture pwiszowaty0  路  4Comments

a-barbieri picture a-barbieri  路  4Comments

Flo-Slv picture Flo-Slv  路  4Comments