React Testing Library 测试场景

137 阅读2分钟

React Testing Library 常见测试场景

0. Install Packages

npm install --save-dev @testing-library/react @testing-library/jest-dom

1. 测试DOM元素

文件App.js

// App.js
import React from "react";

export const App = () => {
  return <h1 data-testid="big-title">Hello React</h1>
}

文件 App.test.js

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

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

    // 不同的方法判断标题的Dom元素是否存在
    expect(screen.getByRole("heading")).toHaveTextContent("Hello React");

    expect(screen.getByRole("heading", { name: "Hello React" })).toBeInTheDocument();

    expect(screen.getByText("Hello React")).toBeInTheDocument();

    expect(screen.getByTestId("big-title")).toHaveTextContent("Hello React");

    // 查询不存在的Dom元素
    expect(screen.queryByTestId("another-big-title")).toBeNull();

  });
});

testing-library提供了许多的方法来获取DOM元素,它们大致分为三类, 优先级是

  1. 查询(Accessible Queries)
    • getByRole
    • etByLabelText
    • getByPlaceholderText
    • getByText
    • getByDisplayValue
  2. 语义查询(Semantic Queries)
    • getByAltText
    • getByTitle
  3. Test IDs
    • getByTestId

优先级参考链接

2. 测试Event

文件App.js

// App.js
import React from "react";

export const App = ({ handleChange }) => {
  return (
    <div>
      <label htmlFor="search">Search</label>
      <input
        id="search"
        type="text"
        value="Search"
        onChange={handleChange}
      />
    </div>
  )
}

文件 App.test.js

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

describe("App", () => {
  test("renders App component", () => {
    const mockSearchFunc = jest.fn();
    // Render
    render(<App handleChange={mockSearchFunc}/>);
    // Act
    fireEvent.change(screen.getByRole("textbox")); 
    // Asset
    expect(mockSearchFunc.call.length).toBe(1);
  });
});

测试组件的事件时,首先要mock事件(jest.fn()),然后通过screen的方法选中元素,由fireEvent来模拟事件的实现,最后进行断言。

fireEvent可以实现常见的事件,如change, click, focus等等。

3. 异步Event - await

文件App.js

// App.js
import React from "react";

const getUser = () => {
  return Promise.resolve({ id: "1", name: "Robin" });
};

export const App = () => {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    const loadUser = async () => {
      const user = await getUser();
      setUser(user);
    };
    loadUser();
  }, []);

  return (
    <div>
      {user ? <p>Signed in as {user.name}</p> : null}
    </div>
  );
}

文件 App.test.js

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

describe("App", () => {
  test("renders App component", async () => {
    // Render
    render(<App />);
    // At first, the dom is not exist
    expect(screen.queryByText(/Signed in as/)).toBeNull();
    // Later, when function exec, the dom is appeared
    expect(await screen.findByText(/Signed in as/)).toBeInTheDocument();
  });
});

find* 方法适合查找异步调用之后的Dom元素,结合await使用

4. 异步Event - 测试包含请求数据的组件

文件App.js

// App.js
import React from 'react';
import axios from 'axios';
 
const URL = 'http://xxx/api/v1/search';
 
export const App = () => {
  const [stories, setStories] = React.useState([]);
  const [error, setError] = React.useState(null);
 
  async function handleFetch(event) {
    let result;
 
    try {
      result = await axios.get(`${URL}?query=React`);
 
      setStories(result.data.hits);
    } catch (error) {
      setError(error);
    }
  }
 
  return (
    <div>
      <button type="button" onClick={handleFetch}>
        Fetch Stories
      </button>
 
      {error && <span>Something went wrong ...</span>}
 
      <ul>
        {stories.map((story) => (
          <li key={story.objectID}>
            <a href={story.url}>{story.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

文件 App.test.js

// App.test.js
import React from 'react';
import axios from 'axios';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
 
import App from './App';
 
jest.mock('axios');
 
describe('App', () => {
  test('fetches stories from an API and displays them', async () => {
    const stories = [
      { objectID: '1', title: 'Hello' },
      { objectID: '2', title: 'React' },
    ];
 
    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(2);
  });
});

Mock axios的方法实现,通过await返回测试数据并进行assert

迁移

  1. Migrate from Enzyme to React Testing Library

  2. 使用 React Testing Library 的 15 个常见错误