RecipeCreator
RecipeCreator

Reputation: 393

Test a react component using test and react-testing-library

I just joined a team where we use react, redux, recompose to construct components to build UI. There aren't any unit tests in the application and there isn't consistent architecture for the application. I decided to take it upon myself to add unit tests using jest and react-testing-library. I succeed with few snapshot tests but I am struggling with unit testing. I am still learning react and pretty new to redux. I would love some suggestion. I am going to share a component which renders a table with column and row. I would love a feedback.

import React, { useEffect, useState } from 'react';
import { compose } from 'recompose';
import { connect } from 'react-redux';

import { clearAll, fetchContacts } from '~/store/resources/contacts/actions';
import { isDevEnv } from '~/utils';

import Sidebar from './Sidebar';
import Table from './Table';
import Toolbar from './Toolbar';

const Contacts = ({ clearAll, fetchContacts, ...props }) => {
  const [searchValue, setSearchValue] = useState('');
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
  const [canonicalFormValues, setCanonicalFormValues] = useState({ active: true });

  useEffect(() => {
    fetchContacts();
    return () => {
      clearAll();
    };
  }, []);

  const closeSidebar = () => {
    if (isDevEnv) {
      console.log('hit close function');
    }
    setIsSidebarOpen(false);
  };

  return (
    <div>
      <Toolbar
        searchValue={searchValue}
        setSearchValue={setSearchValue}
        setIsSidebarOpen={setIsSidebarOpen}
      />
      <Table setCanonicalFormValues={setCanonicalFormValues} />
      <Sidebar
        isSidebarOpen={isSidebarOpen}
        closeSidebar={closeSidebar}
        canonicalFormValues={canonicalFormValues}
      />
      {isDevEnv && (
        <div>
          This is coming from the contact folder
          <br />
          state values:
          <br />
          {JSON.stringify({ searchValue })}
          <br />
          {JSON.stringify({ isSidebarOpen })}
          <br />
          {JSON.stringify({ canonicalFormValues })}
        </div>
      )}
    </div>
  );
};

const mapDispatchToProps = {
  clearAll,
  fetchContacts,
};

export default compose(
  connect(
    null,
    mapDispatchToProps,
  ),
)(Contacts);

Upvotes: 2

Views: 4406

Answers (1)

Drew Reese
Drew Reese

Reputation: 202605

I generally start out with a simple "should render without crashing" test. I prefer to export and test the undecorated component, in your case Contacts.

export const Contacts = ({ clearAll, fetchContacts, ...props }) => { ...

In the test file

import React from 'react';
import { render } from '@testing-library/react';
import { Contacts } from '.';

// mock the other imported components, they should already be tested alone, right?
jest.mock('./Sidebar');
jest.mock('./Table');
jest.mock('./Toolbar');

describe('Contacts', () => {
  it('should render without crashing', () = {
    render(
      <Contacts
        // pass all the props necessary for a basic render
        clearAll={jest.fn()}
        fetchContacts={jest.fn()}
      />
    );
  });
});

At this point I run a code coverage report to see how much I have, then add more tests with varying prop values and/or using the react-testing-library's matchers to target buttons or elements to assert text is visible or trigger callbacks, etc, until I have the coverage I want.

Sometimes some of your components may rely on context provider, and in this case RTL allows you to specify wrappers. For example if your component gets decorated with react-intl for string localization, you can provide a wrapper.

export const Contacts = ({ clearAll, fetchContacts, intl }) => { ...

...

export default compose(
  connect(
    null,
    mapDispatchToProps,
  ),
  injectIntl,
)(Contacts);

Create a wrapper

import { IntlProvider } from 'react-intl';

const IntlWrapper = ({ children }) => (
  <IntlProvider locale="en">{children}</IntlProvider>
);

const intlMock = {
  ...
  formatMessage: message => message,
  ...
};

and to test, specify the wrapper in the render options argument

render(
  <Contacts
    // pass all the props necessary for a basic render
    clearAll={jest.fn()}
    fetchContacts={jest.fn()}
    intl={intlMock}
  />,
  {
    wrapper: IntlWrapper
  }
);

react-testing-library has a lot of documentation, but it is worth reading through. Hope this helps you get going.

Upvotes: 3

Related Questions