An overview of the

Testing-library is the official unit test library recommended by React. The object is Airbnb’s Enzyme. I tried to explain the characteristics of Testing- Library in terms of a popular system of techniques (problem finding, problem analysis, problem solving) :

  1. The designers of Testing- Library saw a problem: Previous Unit tests focused on asserting properties inside components, but developers found this Testing a bit self-deceiving

  2. To boost developers’ confidence in their test cases, they came up with the idea that tests that more closely resembled how the software was used would be considered more reliable

  3. They then designed a bunch of apis to simulate how users (developers) would find or manipulate the DOM

OK, the tet-out -library provides a series of anthropomorphic apis to help us test UI components. For how to personify, see the example below.

The installation

This article uses react as an example. If you use create-react-app, you can skip this chapter. If not, the following two dependencies need to be installed:

yarn add -D @testing-library/react @testing-library/user-event
Copy the code

For the record, testing-library is a library, not a framework. While the Framework follows the Hollywood principle (Don’t call me, I’ll call you), libraries typically require active import methods. Testing-library implements the integration of all major testing frameworks. In this article, we use the React official Jest+testing-library combination, so we also need to install @testing-library/jest-dom:

yarn add - D @testing-library/jest-dom
Copy the code

For convenience, a startup file is usually added to jest to import @testing-library/jest-dom; Of course, you can omit this step, but it is a bit troublesome to add the following sentence to each test file.

// setupTests.js
import "@testing-library/jest-dom";
Copy the code
// package.json
{
  "jest": {
    "setupFilesAfterEnv": ["setupTests.js"]}}Copy the code

Get started

Once the installation is complete, we’ll start our first test case. Write a Hello World component:

// Title.js
import React from "react";
export const Title = () = > <h1>Hello World</h1>;
Copy the code

The React Testing Library (RTL) uses a method called Render to render the React component (VUE, Angular, and Svelte frameworks use the same Testing Library) :

// Title.test.js
import React from "react";
import { Title } from "./Title";
+ import { render } from "@testing-library/react";

describe("Title", () => {
  test("debug Title", () => {
+ render();
  });
});
Copy the code

If beginners want to see the result of render, try screen.debug() :

// Title.test.js
import { render, screen } from "@testing-library/react";

describe("Title".() = > {
  test("debug Title".() = > {
    render(<Title />);

    screen.debug();
  });
});
Copy the code

When I run Jest, the console outputs the following: an HTML document. The React component’s DOM is wrapped in the render function’s default container, .

<body>
  <div>
    <h1>Hello World</h1>
  </div>
</body>
Copy the code

Normally, we write test cases without directly printing the render DOM, but keep in mind that all test methods are based on the render results of the render method.

Select elements

Of course, simply rendering a Document using library methods is not a test case; We need at least one assertion: for example, that an element will appear in a rendered Document. We add the following two lines of code after Render:

// Title.test.js
test("getByText of Title", () => {
  render(<Title />);
+ const $e = screen.getByText("Hello World");
+ expect($e).toBeInTheDocument();
});
Copy the code

Explain:

  1. usinggetByTextFind a contain textHello WorldAn element of
  2. Assert that this element is in Document

Run jEST again and the test passes; A very basic test case is complete.

2. Skipped over or skipped over. 2. Skipped over or skipped over. 3. Skipped over or skipped overCopy the code

In this case, we used getByText — use text to view the target element. Does that sound like a scene where you go to inspect some text in the browser and find the target element? This is what makes the RTL API unique: it simulates the use case operations of the developer.

In addition, there are several apis that can also help us view elements. But did you notice that there’s no selector here? ! This is the anthropomorphic feature mentioned above: you don’t need to know what ID or class is being used inside the component; You only need to be vaguely aware of an HTML tag to start testing.

  • getByRole(‘button’): <button>click me</button>
  • getByLabelText(‘search’): <label for="search" />
  • getByPlaceholderText(‘Search’): <input placeholder="Search" />
  • getByAltText(‘profile): <img alt="profile" />
  • getByDisplayValue(‘Javascript): <input value="JavaScript" />

The search variables

getByText

Let’s move on to getByText. We used getByText(‘Hello World’), which is a full-text match search, and it actually supports regular expressions. The following case can also pass; As long as you know the variable of the text, getByText can help you find the target element.

test("getByText by regular expression".() = > {
  render(<Title />);
  const $e = screen.getByText(/Hello/);
  expect($e).toBeInTheDocument();
});
Copy the code

queryByText

In addition to getByText, RTL provides a similar method called queryByText. If getByText fails to find an element, it throws an exception and the test case breaks. QueryByText returns NULL in this case, so queryBy is often used to assert that an element does not exist in a Document. This kind of test is quite common, such as passing a parameter to a component to make it hide an element.

test("search queryByText of Title".() = > {
  render(<Title />);
  const $e = screen.queryByText(/Onion/);
  expect($e).toBeNull();
});
Copy the code

findByText

A third, similar method called findByText is an asynchronous function; For testing components that require asynchronous rendering:

// AsyncTitle.js
import React, { useState, useEffect } from "react";

export const AsyncTitle = () = > {
  const [user, setUser] = useState(null);

  useEffect(() = > {
    const loadUser = async() = > {await "simulate a promise";
      setUser("Onion");
    };
    loadUser();
  });

  return user && <h1>Hello {user}</h1>;
};
Copy the code
// AsyncTitle.test.js
test("findByText of AsyncTitle".async () => {
  render(<AsyncTitle />);
  const $e = await screen.findByText(/Hello/);
  expect($e).toBeInTheDocument();
});
Copy the code

Multielement selection

All of the above are selecting the element that appears first. Of course, there are all of them, and the apis are similar to the above three sets — getAllBy, queryAllBy, findAllBy. Tagtext is an empty PlaceholderText function. All search returns an array, and the test is similar, but with a loop, I won’t expand it here.

The event

Then we’ll talk about how to test UI events. We don’t look at the source code, just look at the component UI effect.

The function of this component is simple: enter text in the input box, it will display the corresponding text. If you were tester, how would you test? I think there are three specific steps:

  1. Select the input field
  2. Enter text
  3. Verify that the entered text is displayed

Unit test:

// InputTitle.test.js
import userEvent from "@testing-library/user-event";

test("type InputTitle".() = > {
  render(<InputTitle />);
  // Step 1
  const $input = screen.getByRole("textbox");

  // Step 2
  const val = "Hello World";
  userEvent.type($input, val);

  // Step 3
  const $text = screen.getByText(val);
  expect($text).toBeInTheDocument();
});
Copy the code

Isn’t that intuitive?

  1. Find the input field $input; The role of tag is a textbox. It doesn’t matter, getByRole, the console will tell you)

  2. Use userEvent.type to simulate user input

  3. Assert that the corresponding input text is already displayed in the Document

Take a look back at the source code for this component; Have you ever felt that you can write UI tests without knowing the implementation?

// InputTitle.js
import React, { useState } from "react";

export const InputTitle = () = > {
  const [head, setHead] = useState("");

  return (
    <div>
      <h1>{head}</h1>
      <input
        type="text"
        value={head}
        onChange={(e)= > setHead(e.target.value)}
      />
    </div>
  );
};
Copy the code

Finally, the @testing-library/ user-Event library used above provides a complete set of user actions for RTL: in addition to type, there are also several types of user actions that you can try.

  • click(element, eventInit, options)
  • dblClick(element, eventInit, options)
  • type(element, text, [options])
  • upload(element, file, [{ clickInit, changeInit }])
  • clear(element)
  • selectOptions(element, values)
  • deselectOptions(element, values)
  • tab({shift, focusTrap})
  • hover(element)
  • unhover(element)
  • paste(element, text, eventInit, options)
  • specialChars

summary

Many old front ends don’t write Unit Test because UI tests are too hard to write. This period looked at the RTL introductory case, we have not wavered; Well, front-end testing isn’t that hard to write, is it?

This is the first installment of Testing-Library 101, and the next installment will cover some advanced test cases, such as callbacks, asynchronous updates, and error catching.

  • React Testing Library 101 (2)