React Testing Library
Tags: react, testing
Published: 2020-03-24 12:00:58

##Why switch from enzyme to react-testing-library?

While enzyme is intended for unit/integration tests, react-testing-library is for blackbox integration tests. Enzyme gives you access to the internal workings of your components and seduces you to test implementation details. Through the blackbox approach of react-testing-library you are forced to write more user-centered tests and interact with the components from the outside instead of accessing implementation details. 

##[Guiding principles](https://testing-library.com/docs/guiding-principles)

* If it relates to rendering components, then it should deal with DOM nodes rather than component instances, and it should not encourage dealing with component instances.
* It should be generally useful for testing the application components in the way the user would use it. We are making some trade-offs here because we're using a computer and often a simulated browser environment, but in general, utilities should encourage tests that use the components the way they're intended to be used.
* Utility implementations and APIs should be simple and flexible.

##Guidelines

* Use expect explicitly with `toBeInTheDocument()` when testing if element exists with getBy or one of the other queries.
* Only import the `testUtils.js` never import `@testing-library/react`
* Prefer the queries `ByLabelText`, `ByPlaceholderText`, `ByRole`, `ByText` and `ByDisplayValue`, as they reflect the user experience the most.
* If needed use semantic queries like `ByAltText` or `ByTitle`, as the user experience can vary for these attributes across various browsers. 
* Avoid using `data-testid` with `ByTestId` as much as possible, because it requires to change the component just for the test.
* Focus on writing user-centered tests and avoid testing implementation details. Expect what the user will see.

##Apollo, Intl, Redux

To efficiently wrap our test cases in the needed Providers, a custom render function was implemented. This function wraps our test component in Redux, Intl and Apollo. It takes the apolloMocks and the redux initialStore as arguments, to set them as needed in our test case.
The apolloMocks data has to match the query datatype completely otherwise they won't resolve in the test.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gddbwy3njp9ne6ua6462.png)

##Api

A very good resource to get a fast overview is the [testing-library cheatsheet](https://testing-library.com/docs/dom-testing-library/cheatsheet).

###Queries

The following queries are provided to select elements in the DOM:

* ByLabelText
* ByPlaceholderText
* ByText
* ByDisplayValue
* ByAltText
* ByTitle
* ByRole
* ByTestId

These need to be combined with the table of selectors below. 
E.g. `getByLabelText`.

For ByText you can use regex to find all elements that contain a fitting text.

###Difference get, query, find

|   | No Match  |  1 Match | 1+ Match  | Await?  |
|---|---|---|---|---|
| **getBy**  | throw  | return  | throw  | No  |
| **findBy**  | throw  | return  | throw  | Yes  |
| **queryBy**  | null  | return  | throw  | No  |
| **getAllBy**  | throw  | array  | array  | No  |
| **findAllBy**  | throw  | array  | array  | Yes  |
| **queryAllBy**  | []  | array  | array  | No  |


###Async

The library provides multiple functions to deal with asynchronous code, like `wait` and `waitForElementToBeRemoved`. These functions take a callback and an options object as parameters. In the options, you can set a timeout (default: 1000ms) and an interval (default: 50ms) to check the callback function.

###Events

Testing events is done using the `fireEvent(node, event)` function.
It has multiple [helpers](https://github.com/testing-library/dom-testing-library/blob/master/src/event-map.js) for default events that can be used like: `fireEvent.click(node)`.

###[Jest-Dom](https://github.com/testing-library/jest-dom)

Jest-Dom provides us with additional jest matchers for testing.
Some useful examples are:

* toBeInTheDocument
* toHaveTextContent
* toHaveValue
* toBeVisible
* toContainElement

###Our additions

To select text elements that are filled with intl ids a custom selector was implemented, that can be used with all selector types in the table.
E.g. `getByIntlId`.

##Code to copy

Some of the custom code to adapt the framework to our needs.

###Custom renderer
```react
const customRender = (
  ui: React$Element<*>,
  { apolloMocks = [], initialStore = {} }: WrapperParameterType = {}
) =>
  render(
    <Provider store={configureStore(initialStore)}>
      <RawIntlProvider value={intl}>
        <MockedProvider mocks={apolloMocks} addTypename={false}>
          {ui}
        </MockedProvider>
      </RawIntlProvider>
    </Provider>,
    {
      queries: {
        ...queries,
        ...intlQueries,
      },
    }
  );
```

###Custom query
```react
export const buildIntlQueries = (intl: string) => {
  const queryAllByIntlId = (container, intlId, options, ...rest) => {
    const text = intl.formatMessage({ id: intlId, ...options });
    return queryAllByText(container, text, ...rest);
  };

  const getMultipleError = (_, intlId) =>
    `Found multiple elements with the intl id: ${intlId}`;
  const getMissingError = (_, intlId) =>
    `Unable to find an element with the intl id: ${intlId}`;

  const [
    queryByIntlId,
    getAllByIntlId,
    getByIntlId,
    findAllByIntlId,
    findByIntlId,
  ] = buildQueries(queryAllByIntlId, getMultipleError, getMissingError);
  return {
    queryByIntlId,
    getAllByIntlId,
    getByIntlId,
    findAllByIntlId,
    findByIntlId,
  };
};

```