Muriwai

October 20, 2018

Testing React and I18next with Jest

In my last blog post I showed you how to use I18next within my React application. In this post I'd like to share how to test your components that contain strings generated with I18next. In my Photo Locations project I use Jest and Enzyme to test my code.

So first of all, if you haven't already, install Jest and Enzyme using either Yarn or npm. Follow the instructions in the Jest tutorial for React..

yarn add jest enzyme

Next in your main directory of your project, create a folder called __mocks__ because you won't actually want to use your live configuration for your tests. In the __mocks__ folder, create a file called react-i18next.js and copy the following code which can also be found in the I18next documentation into the file.

const React = require ('react');
const reactI18next = require ('react-i18next');

const hasChildren = node =>
  node && (node.children || (node.props && node.props.children));

const getChildren = node =>
  node && node.children ? node.children : node.props && node.props.children;

const renderNodes = reactNodes => {
  if (typeof reactNodes === 'string') {
    return reactNodes;
  }

  return Object.keys (reactNodes).map ((key, i) => {
    const child = reactNodes[key];
    const isElement = React.isValidElement (child);

    if (typeof child === 'string') {
      return child;
    }
    if (hasChildren (child)) {
      const inner = renderNodes (getChildren (child));
      return React.cloneElement (child, {...child.props, key: i}, inner);
    } else if (typeof child === 'object' && !isElement) {
      return Object.keys (child).reduce (
        (str, childKey) => `${str}${child[childKey]}`,
        ''
      );
    }

    return child;
  });
};

module.exports = {
  // this mock makes sure any components using the translate HoC receive the t function as a prop
  withNamespaces: () => Component => props => (
    <Component t={k => k} {...props} />
  ),
  Trans: ({children}) => renderNodes (children),
  NamespacesConsumer: ({children}) => children (k => k, {i18n: {}}),

  // mock if needed
  Interpolate: reactI18next.Interpolate,
  I18nextProvider: reactI18next.I18nextProvider,
  loadNamespaces: reactI18next.loadNamespaces,
  reactI18nextModule: reactI18next.reactI18nextModule,
  setDefaults: reactI18next.setDefaults,
  getDefaults: reactI18next.getDefaults,
  setI18n: reactI18next.setI18n,
  getI18n: reactI18next.getI18n,
};

Once that's done, it's time to create a folder called tests in the root directory of your project to start writing some tests.

For this blog, we will be testing that the h1 tag in my About.jsx component displays correctly. To do this, create a file called "About.test.js" inside the tests folder.

First import all the dependencies:

import React from 'react'
import { configure, shallow, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import {About} from '../../../client/components/About'

Next create an adapter to be able to run Enzyme tests:

configure({adapter: new Adapter()})

Then you can start writing the actual test:

test('<About/> renders h1 component with given content', () => {
  const expected = 'About Photo Locations'
  const wrapper = shallow(<About t={(k) => 'About Photo Locations'} />)
  const actual = wrapper.find('h1').text()

  expect(actual).toEqual(expected)
})

The first line simply describes what the test is expected to do. Create the variable "expected" and give the text which is expected to appear in the About page header. Next shallow render the About component and pass it the text you want it to render, rather than calling up the json file and looking up the string. Then create a variable called actual to look up the text that is contained in the h1 tag. Last but not least compare the "expected" variable to the "actual" variable to ensure they match.

To find out more about the different rendering methods that Enzyme uses, such as shallow, mount or render, check out the Enzyme documentation.

To run the test, type:

jest about

into the console.

Running jest about

Something to watch out for

Because the mock react-i18next.js file contains the ES6 spread operator, which is not initially supported by Jest, I had to add:

yarn add @babel/plugin-syntax-object-rest-spread

I also upgraded Babel to version 7 to support this.

Have you tried writing tests for I18next with Jest yet? Feel free to share your experience in the comments section below.