React-testing-library overview

Original author: Kent C. Dodds
  • Transfer
In a material we are translating today, Kent Dodds talks about a library of his own design for testing React applications, a react-testing-library , in which he sees a simple tool that can replace enzyme and help write high-quality tests using advanced developments in this field .


The author of the material said that he had been thinking about something like this for a long time, and as a result, around the middle of last month, he decided to start developing a testing library that would suit him. In particular, in the enzyme, he did not like the fact that most of the features of this library incline the developer to not the best test preparation methods that can harm the project. As a result, he got a simple but self-contained set of tools for testing the React DOM.

React-testing-library overview


Suppose you are going to write tests for React components. You want these tests to be conveniently maintained. In addition, you need to ensure that the tests do not rely on the implementation details of the components and test the components in conditions that are close to the real scenarios of their use. In addition, you strive to ensure that the tests remain working in the long run, that is, you want the refactoring of the components (that is, changing their implementation, but not the functionality) not to violate the testing system and not force you or your team constantly rewrite tests, slowing down project work.

What to choose to achieve these goals? In our case, the answer to these questions was a library react-testing-library, a minimalistic solution designed to test React components.


Logo react-testing-library

This library gives the developer simple tools, built on the basis of, react-domand react-dom/test-utild, moreover, the library is designed so that those who use it, without any problems would apply best practices in testing. It is based react-testing-libraryon the following principle: the more the testing process resembles a real session with an application, the more confidently we can say that when the application gets into production, it will work as expected.

As a result, instead of working with instances of rendered React components, tests will interact with real DOM nodes. The mechanisms provided by the library make calls to the DOM in the same way that project users would do. Elements are searched by the texts of their labels (this is what users do), links and buttons are also searched by their texts (and this is typical for users). In addition, here, as an "emergency exit", it is possible to apply the search for elements by data-testid. This is a common practice that allows you to work with elements whose signatures are meaningless or impractical for this purpose.

The considered library helps to increase the availability of applications, helps bring tests closer to real-world work scenarios.

The library react-testing-libraryis a replacement for enzyme . Using it enzyme, you can follow the same principles that are laid down in the library considered here, but in this case they are more difficult to adhere to because of the additional funds provided enzyme(that is, all that helps in the details of the implementation of the tests). Details about this can be found here .

In addition, although react-testing-libraryintended for react-dom, it is also suitable for React Native through the use of this small settings file .

It’s worth mentioning right away that this library is not a means to run tests or a framework. In addition, it is not tied to a certain testing framework (although we recommend Jest, it is just a tool that we prefer to use, in general, the library will work with any framework, and even in the CodeSandbox environment !).

Practical example


Consider the following code demonstrating a practical example of working with react-testing-library.

import React from 'react'
import {render, Simulate, wait} from 'react-testing-library'
// Тут добавляется средство проверки ожиданий
import 'react-testing-library/extend-expect'
// Mock-объект находится в директории __mocks__
import axiosMock from 'axios'
import GreetingFetcher from '../greeting-fetcher'
test('displays greeting when clicking Load Greeting', async () => {
  // Этап Arrange
  axiosMock.get.mockImplementationOnce(({name}) =>
    Promise.resolve({
      data: {greeting: `Hello ${name}`}
    })
  )
  const {
    getByLabelText,
    getByText,
    getByTestId,
    container
  } = render()
  // Этап Act
  getByLabelText('name').value = 'Mary'
  Simulate.click(getByText('Load Greeting'))
  // Подождём разрешения mock-запроса `get`
  // Эта конструкция будет ждать до тех пор, пока коллбэк не выдаст ошибку
  await wait(() => getByTestId('greeting-text'))
  // Этап Assert
  expect(axiosMock.get).toHaveBeenCalledTimes(1)
  expect(axiosMock.get).toHaveBeenCalledWith(url)
  // собственный матчер!
  expect(getByTestId('greeting-text')).toHaveTextContent(
    'Hello Mary'
  )
  // снэпшоты отлично работают с обычными узлами DOM!
  expect(container.firstChild).toMatchSnapshot()
})

The most important thing that can be learned from this example is that the tests resemble working with a real user application.

We continue to analyze this code.

GreetingFletchercan output some HTML code, for example, this:

         

When working with these elements, the following sequence of actions is expected: set a name, click on the button Load Greeting, which will cause a request to the server to load some text in which the given name is used.

In the test, you need to find the field , as a result, you can set its parameter valueto a certain value. Common sense dictates that there can use the property idin CSS-selector: #name-input. But is this what the user does in order to find the input field? Definitely not so! The user looks at the screen and finds a field with a signature Namein which he enters data. Therefore, this is exactly what our test does with getByLabelText. It discovers the control based on its label.

Often in tests based onenzyme, to search for a button, for example, with an inscription Load Greeting, use the CSS selector or search by the displayNameconstructor of the component. But when the user wants to download some text from the server, he does not think about the details of the implementation of the program, instead he searches for a button with an inscription Load Greetingsand clicks on it. This is exactly what our test does with the help of an auxiliary function getByText.

In addition to this, the design wait, again, simulates user behavior. Here organized waiting for the appearance of text on the screen. The system will wait as long as necessary. In our test, a mock object is used for this, so the text is output almost instantly. But our test does not care how long it takes. We do not need to use in the test setTimeoutor something like that.

We just tell the test: "Wait for the greeting-text node to appear." Pay attention to the fact that in this case the attribute is used data-testid, the very "emergency exit" used in situations where it does not make sense to look for elements using some other mechanism. In such cases, it is data-testiddefinitely better than alternative methods.

API Overview


At the very beginning, the library gave the developer only a method queryByTestId. Read about it here . However, due to the response to the above publication and this fantastic performance , additional methods have been added to the library.

Details about the library and its API can be found in the official documentation . Here is a general overview of its features.

  • Simulate - Re-export from the Simulate Helper react-dom/test-utils.
  • wait - allows you to organize in tests the wait for an indefinite period of time. Usually, you should use mock objects for API calls or animations , but even when working with immediately resolved promises, you need the tests to wait for the next tick of the event loop. The method is waitgreat for this. (Many thanks to Lukasz Gandecki, who suggested this as a replacement for the API flushPromises, which is now considered deprecated).

  • render : the whole essence of the library is hidden here. In fact, this is a fairly simple function. She creates an element divwith document.createElement, then uses ReactDOM.renderto output data to this one div.

The function renderreturns the following objects and auxiliary functions:

  • container : the element divinto which the component was rendered.
  • unmount : a simple wrapper around ReactDOM.unmountComponentAtNodeto unmount a component (for example, to simplify testing componentWillUnmount).
  • getByLabelText : Gets the form control based on its label.
  • getByPlaceholderText : placeholders are not very good alternatives to labels, but if that makes sense in a particular situation, you can use them.
  • getByText : gets the element by its textual content.
  • getByAltText : gets the element (sort of) by the value of its attribute alt.
  • getByTestId : gets the element by its attribute data-testid.

Each of these helper get-methods displays an informative error message if the item cannot be found. In addition, there is a set of similar query methods (like queryByText). They, instead of throwing an error in the absence of an element, return null, which can be useful if you need to check the DOM for the absence of an element.

In addition, to these methods, to search for the desired item, you can pass the following:

  • Case insensitive string: for example, lo worldwill match Hello World.
  • Regular expression: for example, /^Hello World$/will match Hello World.
  • A function that accepts text and an element: for example, using a function (text, el) => el.tagName === 'SPAN' && text.startsWith('Hello')will select an element spanwhose text content starts with Hello.

It should be noted that thanks to Anto Ryan , the library has its own matchmakers for Jest.


Summary


The main feature of the library react-testing-libraryis that it does not have auxiliary methods that allow you to test the implementation details of components. It aims to develop tests that foster best practices in testing and software development. We hope you find the react-testing-library useful.

Dear readers! Do you plan to use react-testing-library in your projects?


Also popular now: