react-i18next:: You will need to pass in an i18next instance by using initReactI18next warning thrown in Jest unit test

Viewed 6765

I am using react-i18next in my app to great effect but when I run the unit tests against my component:

const OptionList = ({
  definition,
  name,
  status = EMutationStatus.IDLE,
  onChange = () => null,
  value = [],
}: IOptionListProps): React.ReactElement => {
  const { t } = useTranslation();
  const { options } = definition;
  return (
    <Select
      name={name}
      data-testid="optionList"
      id={name}
      placeholder={t('COMMON.PLEASE_SELECT')}
      onChange={e => onChange(e.currentTarget.value)}
      defaultValue={value[0]}
      disabled={status === EMutationStatus.LOADING}
    >
      {options.map((option: string): React.ReactElement => {
        return (
          <option key={option} value={option}>
            {option}
          </option>
        );
      })}
    </Select>
  );
};

It throws the following warning when running the unit test suite:

react-i18next:: You will need to pass in an i18next instance by using initReactI18next

I am a bit lost as I have set up react-i18next for unit testing in a custom render function for react-testing-library.

My i18n instance for tests:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

i18n.use(initReactI18next).init({
  lng: 'cimode', // setting lng to 'cimode' will cause the t function to always return the key.
  // ----- ^
  // have a common namespace used around the full app
  ns: ['translations'],
  defaultNS: 'translations',

  interpolation: {
    escapeValue: false, // not needed for react!!
  },

  resources: { en: { translations: {} }, zh: { translations: {} }, th: { translations: {} } },
});

export default i18n;

And my custom render function is as follows:

export function render(
  ui: RenderUI,
  { wrapper, ...options }: RenderOptions = {}
): RenderResult {
  if (!wrapper) {
    // eslint-disable-next-line no-param-reassign
    wrapper = ({ children }) => (
      <BrowserRouter>
        <ChakraProvider resetCSS theme={theme}>
          <QueryClientProvider client={testQueryClient}>
            <I18nextProvider i18n={i18n}>{children}</I18nextProvider>
          </QueryClientProvider>
        </ChakraProvider>
      </BrowserRouter>
    );
  }

  return defaultRender(ui, { wrapper, ...options });
}

It is only a warning so not a big deal but am confused as to why this is throwing here as I believe I have set up the Provider as outlined in the docs here:

https://react.i18next.com/misc/testing

An anyone help me get rid of this warning?

UPDATE

I tried adding the suggested jest mock of the useTranslation hook from the same testing link:

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): string => str,
    };
  },
}));

But still I get the same warning.

6 Answers

This worked for me. You can find more info in the official documentation.

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) => str,
      i18n: {
        changeLanguage: () => new Promise(() => {}),
      },
    };
  },
}));

Did you try to call i18n.init() inside beforeEach hook? And i18n should be the instance of your instantiated i18n instance.

The only difference that in my case I have named export of the i18n instance.

import { i18n } from "../../services/Internationalization";

describe('testSuite', () => {
   beforeEach(() => {
    i18n.init();
  });
});

And in i18n.ts I have to do:

i18n.use(initReactI18Next).init(...some configs);
export { i18n };

One modification for RyanP13s proposal, I return an array as expected return value from useTranslation(), works now fine for me:

jest.mock('react-i18next', () => ({
  useTranslation: () => {
    return [/* t */ (str: string): string => str, /* i18n */ { language: 'cimode' }]
  },
}))

While it's not the official solution, I found that creating an instance of i18n specifically for testing worked nicely. It renders the actual strings and you don't have to mock useTranslation, which is the usual recommendation.

Create a file called i18nForTests.

// i18nForTests.js

import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import { fooTranslation } from '/translations/foo'
import { barTranslation } from '/translations/bar'

i18n
  .use(initReactI18next)
  .init({
    lng: 'foo',
    fallbackLng: 'foo',
    ns: ['foobar'],
    defaultNS: 'foobar',
    debug: true,
    resources: {
      'foo': fooTranslation,
      'bar': barTranslation
    }
  })
  
 export default i18n

Then import and initialize this heinous hackery in jest.setup.js.

Note: if you don't have jest.setup.js, use this in whichever setup file you do have (see jest.config.js > setupFiles). If you don't have any setup files, create one.

// jest.setup.js

import i18n from 'i18nForTests'

i18n.init()

Getting rid of the warning is a mere one-liner in most setups:

Simply include the module in which i18next is initialized into the setupFiles-array in jest.config.js.

The discussion in this thread took things a bit further by contemplating creating a mock for the useTranslation hook, which seems unnecessary just to get rid of the warning. However, if you want a jest.mock() to be loaded before every test, thesetupFilesAfterEnv-array probably is the way to go.


For the sake of clarity, this is what my jest.config.js now looks like:

const config = {
  rootDir: '../',
  preset: '@redwoodjs/testing/config/jest/web',
  setupFiles: ['web/src/i18n'],   // ← that was all, folks!
}

module.exports = config

NB: this is a RedwoodJS project and web/src/i18n.js is the module that includes the initialization module for i18next also used in the actual main App.tsx.

What worked best for me (yet very similar to the update you posted) is to first do jest.mock for the react-i18next import and on the "global" beforeEach I mock the return value for useTranslation:

import { useTranslation } from 'react-i18next';

jest.mock('react-i18next');

describe('OptionList', () => {
    beforeEach(() => {
        useTranslation.mockReturnValue({ t: key => key });
    });

    // your component tests here
});
Related