React Testing Library 是测试 React 组件的测试库,与jest配合使用。功能类比于 Airbnb 的 Enzyme
github参考代码
1. 测试组件渲染
// specs/1.render.spec.jsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import { App } from "../src/App";
test.skip("renders the correct content", () => {
const root = document.createElement("div");
ReactDOM.render(<App />, root);
expect(root.querySelector("h1").textContent).toBe("TODOs");
expect(root.querySelector("label").textContent).toBe(
"What needs to be done?"
);
expect(root.querySelector("button").textContent).toBe("Add #1");
})
2. 测试获取DOM内容
// specs/2.queryDom.spec.jsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import { getQueriesForElement } from "@testing-library/dom";
import { App } from "../src/App";
test.skip("renders the correct content", () => {
const root = document.createElement("div");
ReactDOM.render(<App />, root);
const { getByText, getByLabelText } = getQueriesForElement(root);
getByText("TODOs");
getByLabelText("What needs to be done?");
getByText("Add #1");
});
3. 使用react库测试渲染
render(<App />)
渲染组件screen.debug()
在命令行上运行测试后,输出App组件的HTML
// specs/3.reactLib.spec.jsx
import { render, screen} from "@testing-library/react";
test('renders App component', () => {
render(<App />); // 渲染组件
screen.debug(); // 在命令行上运行测试后,输出App组件的HTML(所以先渲染组件,再调试是否存在某些文件)
});
test("renders the correct content", () => {
const { getByText, getByLabelText } = render(<App />);
getByText("TODOs");
getByLabelText("What needs to be done?");
getByText("Add #1");
});
test("renders the correct content by screen", () => {
render(<App />);
// expect(screen.getByText(/TODOs~~~~/)).toBeNull(); 不适合用来判断内容是否为空
expect(screen.queryByText(/TODOs~~~~/)).toBeNull();
// 用于异步操作,等待有这个文本时
// await screen.findByText(/Signed in as/);
});
4. fireEvent函数来模拟终端用户的交互
fireEvent函数接受一个元素(这里是文本框角色的输入字段)和一个事件(这里是一个值为“JavaScript”的事件)。调试函数的输出应该显示事件前后的HTML结构;您应该可以看到输入字段的新值得到了适当的呈现
// specs/4.simulate.spec.jsx
import { fireEvent, render } from "@testing-library/react";
test("allows users to add items to their list", () => {
const { getByText, getByLabelText } = render(<App />);
const input = getByLabelText("What needs to be done?");
fireEvent.change(input, { target: { value: "RTL Presentation Slides" } }); // 模拟输入内容
fireEvent.click(getByText("Add #1")); // 模拟点击
getByText("RTL Presentation Slides"); // 判断是否有此内容
});
- fireEvent:只有change事件
- userEvent用户事件库:建立在fireEvent API上,有change、keyDown、keyPress和keyUp事件(尽可能使用userEvent而不是fireEvent)
import userEvent from '@testing-library/user-event';
test("test userEvent api", () => {
const { getByText, getByLabelText, getByTestId } = render(<App />);
const input = getByLabelText("What needs to be done?");
const addBtn = getByTestId("add-btn");
userEvent.type(input, "RTL Presentation Slides"); // 输出内容
userEvent.click(addBtn); // 点击
getByText("RTL Presentation Slides"); // 判断是否有此内容
});
5. 回调处理程序
使用Jest来模拟外部模块
jest 模拟api
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); // 被调用时
});
6. 测试异步代码
import { fireEvent, render, waitFor } from "@testing-library/react";
import { App } from "../src/AsyncApp";
// Normally you can mock the entire module using jest.mock("./api")
import { api } from "../src/api";
const mockCreateItem = (api.createItem = jest.fn()); // 创建一个模拟函数
test("allows users to add items to their list", async () => {
const todoText = "RTL Presentation Slides";
mockCreateItem.mockResolvedValueOnce({ id: 123, text: todoText }); // 调用函数并传入参数
const { getByText, getByLabelText } = render(<App />);
const input = getByLabelText("What needs to be done?");
fireEvent.change(input, { target: { value: todoText } });
fireEvent.click(getByText("Add #1"));
expect(mockCreateItem).toHaveBeenCalledTimes(1);
expect(mockCreateItem).toHaveBeenCalledWith(
"/items",
expect.objectContaining({ text: todoText })
);
await waitFor(() => getByText(todoText));
});
其他使用
搜索类型
- getByText:
<button>Add</button>
,判断是否存在getByText('Add')
- getByRole:函数通常用于通过aria-label属性检索元素
<button type="button" >
- getByLabelText :
<label for="search" />
- getByPlaceholderText:
<input placeholder="Search" />
- getByAltText:
<img alt="profile" />
- getByDisplayValue:
<input value="JavaScript" />
describe('App', () => {
test('renders App component', () => {
render(<App />);
screen.getByRole(''); // 可以使用 expect(screen.getByRole('textbox')).toBeInTheDocument();
});
});
/*
输出
Unable to find an accessible element with the role ""
Here are the accessible roles:
document:
Name "":
<body />
--------------------------------------------------
textbox:
Name "Search:":
<input
id="search"
type="text"
value=""
/>
--------------------------------------------------
*/
搜索变量
三个搜索变量getBy, queryBy和findBy
它们都返回一个元素数组,并且可以再次与搜索类型(例如:Text, Role, PlaceholderText, DisplayValue)关联
来可以访问的相同搜索类型进行扩展
queryByText
queryByRole
queryByLabelText
queryByPlaceholderText
queryByAltText
queryByDisplayValue
findByText
findByRole
findByLabelText
findByPlaceholderText
findByAltText
findByDisplayValue
findAllByRole
- getBy和queryBy的区别?
getBy: 去断言一个不存在的元素时,进行断言之前会抛出一个错误,因为它找不到包含该文本的元素。为了断言不存在的元素,我们可以用queryBy交换getBy。每次断言某个元素不存在时,请使用queryBy
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
describe('App', () => {
test('renders App component', () => {
render(<App />);
expect(screen.queryByText(/Searches for JavaScript/)).toBeNull();
});
});
- 对于任何尚未存在但最终将存在的元素,使用findBy而不是getBy或queryBy
describe('App', () => {
test('renders App component', async () => {
render(<App />);
expect(screen.queryByText(/Signed in as/)).toBeNull();
expect(await screen.findByText(/Signed in as/)).toBeInTheDocument();
});
});
断言功能
toBeDisabled
toBeEnabled
toBeEmpty
toBeEmptyDOMElement
toBeInTheDocument:和toBeNull检查元素是否存在(和)
toBeInvalid
toBeRequired
toBeValid
toBeVisible
toContainElement
toContainHTML
toHaveAttribute
toHaveClass
toHaveFocus
toHaveFormValues
toHaveStyle
toHaveTextContent
toHaveValue
toHaveDisplayValue
toBeChecked
toBePartiallyChecked
toHaveDescription