Unit Testing of React Components with Testing Library
Tags: javascript, testing, webdev, tutorial
Published: 2022-03-11 04:16:29

React Testing Library is an alternative to Enzyme. This puts developer in the shoes of end user of react application.

## Jest Vs React Testing Library
**Jest** is the test runner and gives the ability to run the test from the command line. When we write `npm test` or `npm run test` it is jest responsibility to collect all the files ending with .test.js, run each test cases and show pass, fail results. **React testing Library** provides us with the functions to work with the DOM elements like render, fireEvent, waitFor, screen. Jest provides us functions for test suites, test cases, and assertions in the form of `describe-block, test-block`. A test suite can have multiple test cases and a test case doesn't have to be in a test suite.

```
import sum from './math.js';

describe('sum', () => {
  test('sums up two values', () => {
    expect(sum(2, 4)).toBe(6);
  });
});
```

## Render a component
Here we discuss the way to render react components.

```
import React from 'react';

const title = 'Hello React';

function App() {
  return <div>{title}</div>;
}

export default App;
```

## Selecting Components
React Testing Library offers us tools to search functions to grab elements. These elements are then used for assertions or user interactions.

```
import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () => {
  test('renders App component', () => {
    render(<App />);

    // implicit assertion
    // because getByText would throw error
    // if element wouldn't be there
    screen.getByText('Search:');

    // explicit assertion
    // recommended
    expect(screen.getByText('Search:')).toBeInTheDocument();
  });
});
```

Use debug function to check what is rendered on the screen if not sure.

```
import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () => {
  test('renders App component', () => {
    render(<App />);

    // fails
    expect(screen.getByText('Search')).toBeInTheDocument();

    // succeeds
    expect(screen.getByText('Search:')).toBeInTheDocument();

    // succeeds
    expect(screen.getByText(/Search/)).toBeInTheDocument();
  });
});
```

## Search Types

1. getByText - As seen in the above example, it is used to select an element by text. 
2. getByLabelText: `<label for="search" />`
3. getByPlaceholderText: `<input placeholder="Search" />`
4. getByAltText: `<img alt="profile" />`
5. getByDisplayValue: `<input value="JavaScript" />`

We also have two more search variants queryBy and findBy. The main difference between all is getBy returns an element or an error. If it doesn't find it throws error. In order to assert elements which aren't there we can exchange getBy with queryBy. The findBy is used for asynchronous elements. After initial render the component change the screen with response from API. If we want to test the component over the stretch of its first render to its second render due to the resolved promise, we have to write an async test, for this purpose we use findBy

```
import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () => {
  test('renders App component', async () => {
    render(<App />);

    expect(screen.queryByText(/Signed in as/)).toBeNull();

    expect(await screen.findByText(/Signed in as/)).toBeInTheDocument();
  });
});
```
We can always use `screen.debug();` to verify our results. If you assert for a missing element, use queryBy. Otherwise default to getBy. 

To assert multiple elements all search variants can be extended with the All word getAllBy, queryAllBy, findAllBy. Assertive functions happen on the right hand-side of your assertion. In the previous tests, you have used two assertive functions: toBeNull and toBeInTheDocument. Usually all these assertive functions origin from Jest. However, React Testing Library extends this API with its own assertive functions like toBeInTheDocument.

## User Interaction: Fire Events
Till now we have rendered components, selected elements to simulate interactions of end user fireEvent function is used.

```
    fireEvent.change(screen.getByRole('textbox'), {
      target: { value: 'JavaScript' },
    });
```
The fireEvent takes an element(here textbox) where change needs to be made and the event(here JavaScript).

React Testing Library comes with an extended user event library which builds up on top of the fireEvent API. fireEvent.change() triggers only a change event whereas userEvent.type triggers a change event, but also keyDown, keyPress, and keyUp events. Whenever possible, use userEvent over fireEvent when using React Testing Library.

```
await userEvent.type(screen.getByRole('textbox'), 'JavaScript');
```

## Change Handlers
Sometimes we donot have state or side-effects, but inputs as props and output as JSX and callbacks. Lets consider the below example.

```
function Search({ value, onChange, children }) {
  return (
    <div>
      <label htmlFor="search">{children}</label>
      <input
        id="search"
        type="text"
        value={value}
        onChange={onChange}
      />
    </div>
  );
}
```
We use a utility from Jest to mock the onChange function which is passed to the component.

```
describe('Search', () => {
  test('calls the onChange callback handler', () => {
    const onChange = jest.fn();

    render(
      <Search value="" onChange={onChange}>
        Search:
      </Search>
    );

    fireEvent.change(screen.getByRole('textbox'), {
      target: { value: 'JavaScript' },
    });

    expect(onChange).toHaveBeenCalledTimes(1);
  });
});
```


## References
1. [React Testing Library Tutorial By ROBIN WIERUCH](https://www.robinwieruch.de/react-testing-library/)
2. [How to Start Testing Your React Apps... By Ibrahima Ndaw](https://www.freecodecamp.org/news/8-simple-steps-to-start-testing-react-apps-using-react-testing-library-and-jest/#prerequisites)
3. [Official Docs](https://testing-library.com/)