React testing library

Test JavaScript the way user would interact with the component

Motivation

  • Write maintainable test
  • Write test how user is going to test
  • Write test hiding the implementational details.

About React testing library

This is a very lightweight solution for testing React components. It is built on top of “react-dom” and “react-dom/test-utils” and provides light utility functions to encourage better testing practice.

Created by : Kent C. Dodds

The guiding principle is

  • The more your test resembles the way your software is used, the more confidence they can give you.

Installation

  • React testing library comes default with 'create-react-app'.
  • This library has a peer dependency on react and react-dom.
  • Optional : You can install “@testing-library/jest-dom” so you can use the custom jest matcher.
npm install --save-dev @testing-library/react

Using npm

Using yarn

yarn add --dev @testing-library/react

Some issues faced

  • Incase you have a dependency on 16.8, there would be a warning, “component not wrapped in act”, can be suppressed.
  • While running coverage, Error was “Nothing was returned from render”, while test was working fine.

Test: Component

import React from 'react';
const title = "Hello World";
function App() {
  return <h1>{title}</h1>
}
export default App;

Component Code

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

import App from './App';

describe('App', () => {
  test("renders the App component", () => {
    render(<App />);
    screen.debug();
  });	
});

Test File

screen.debug() would return the whole DOM to the terminal

Test: Component

import React from 'react';
const title = "Hello World";
function App() {
  return (
    <div>
      <label for="search">Search:</label>
      <input type="text" id="search" />
    </div>
  );
}
export default App;

Component Code

import {render, screen} from '@testing-library/react';
... All the import statements

describe('App', () => {
  test("Find the search text", () => {
    render(<app />);
    expect(screen.getByText(/Search:/)).toBeInTheDocument();
    expect(screen.getByRole('textbox')).toBeInTheDocument();
  });
});

Test File

Selecting Elements

In order to assert, we need to select elements first. There are 3 ways :

  • queryBy* : returns element or null if element not found
  • getBy* : returns element or throw error if not found
  • findBy* : used when there is asynchronous element.

Example: 

getByText / queryByText / findByText

getByRole / queryByRole / findByRole

Selecting Elements

  • Text : <p>Some text</p>
  • LabelText : <label>Some text</label>
  • PlaceholderText : <input type="text" placeholder="some text" />
  • AltText : <img alt="some text" />
  • DisplayValue : <input value="some value" />
  • TestId : <div data-testid="some-value"></div>

Below are the ways all the 3 variants would apply

Selecting Elements

For selecting multiple elements in the DOM, we can use any of getAllBy, queryAllBy, findAllBy. 

 

It would return an array of elements

Assertive Functions

All the assertion are from jest. However, RTL extends from jest to give more meaningful assertion. Full list here. Below are some list.

  • toBeDisabled
  • toBeEnabled
  • toBeEmpty
  • toBeEmptyDOMElement
  • toBeInTheDocument
  • toBeInvalid
  • toBeRequired
  • toBeValid
  • toBeVisible
  • toHaveDisplayValue
  • toBeChecked
  • toContainElement
  • toContainHTML
  • toHaveAttribute
  • toHaveClass
  • toHaveFocus
  • toHaveFormValues
  • toHaveStyle
  • toHaveTextContent
  • toHaveValue
  • toBePartiallyChecked
  • toHaveDescription

Testing Events

RTL has 'fireEvent' to trigger user interaction. FireEvent takes a node and the event. 

import { screen, render, fireEvent } from '@testing-library/react';
... All the other imports goes here

describe("testing the earlier label and text", () => {
  test("expect the input to be in the document", () => {
    render(<App />);
    expect(screen.queryByLabelText(/some label/)).toBeInTheDocument();
    expect(screen.queryByPlaceholderText(/search here/)).toBeInTheDocument();
    expect(screen.getByRole(/Search Button/)).toBeDisabled();
    fireEvent.change(screen.getByRole('textbox'), {
      target: { value: "the changed text" }
    });
    expect(screen.getByRole(/Search Button/)).toBeEnabled();
  });
});
fireEvent(node: HTMLElement, event: Event);

Or

fireEvent(EventName)(HTMLElement, EventProperties);

Find the full set of events that can be used are here.

Testing Events: user-events

user-event is a companion library for Testing Library that provides more advanced simulation of browser interactions than the built-in fireEvent method.

npm install --save-dev @testing-library/user-event
// or
yarn add @testing-library/user-event --dev
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('click', () => {
  render(
    <div>
      <label htmlFor="checkbox">Check</label>
      <input id="checkbox" type="checkbox" />
    </div>
  )

  userEvent.click(screen.getByText('Check'))
  expect(screen.getByLabelText('Check')).toBeChecked()
})

Creating Events

To get a reference of the event triggered, use 'createEvent'.

const myEvent = createEvent.click(node, { button: 2 })
fireEvent(node, myEvent)
// myEvent.timeStamp can be accessed just like any other properties from myEvent

Custom Events

Creating a generic event

// simulate the 'input' event on a file input
fireEvent(
  input,
  createEvent('input', input, {
    target: { files: inputFiles },
    ...init,
  })
)

Mocking Events

It might not be required to test the full functionality and just mock the events. 

import axios from 'axios';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
...Other imports
jest.mock('axios');
describe('App', () => {
  test('fetches stories from an API and displays them', async () => {
    const stories = [
      { objectID: '1', title: 'Hello' },
    ];
    axios.get.mockImplementationOnce(() =>
      Promise.resolve({ data: { hits: stories } })
    );
    render(<App />);
    await userEvent.click(screen.getByRole('button'));
    const items = await screen.findAllByRole('listitem'); 
    expect(items).toHaveLength(1);
  });
});

React testing library

By Jagat Jeevan Sahoo

React testing library

To write the test, the use would use.

  • 301