单元测试

常用测试工具 Jest,用于测试 js 相关.
测试 react 使用@testing-library/react
测试 react-hook 使用@testing-library/react-hooks

案例

1
yarn add @testing-library/react-hooks msw -D

新建__tests__文件夹,约定__为测试使用文件夹.

单元测试

__tests__/http中测试http模块.
下面模块用于模拟异步请求.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { setupServer } from "msw/node";
const server = setupServer();

// beforeAll是Jest测试库中一个方法,表示执行所有测试之前先执行
beforeAll(() => server.listen());
//每次测试完毕都重置mock路由
afterEach(() => server.resetHandlers());
//所有测试完毕后,关闭mock路由
afterAll(() => server.close());

// 开始测试,第二个参数是回调函数就是测试内容
test("http异步发送请求", async () => {
const endpoint = "test-endpoint";
const mockResult = { mockValue: "mock" };

// 使用msw模拟mock
server.use(
// rest是msw中用于restful接口的方法
rest.get(`${apiUrl}/${endpoint}`, (req, res, ctx) => {
res(ctx.json(mockResult));
})
);
// 使用http模块返回mock值
const result = await http(endpoint);
// 期望http的返回值和mock数据相等(注意不是完全相等,完全是toBe)
expect(result).toEqual(mockResult);
});

test("http请求时携带token", async () => {
const token = "FAKE_TOKEN";
const endpoint = "test-endpoint";
const mockResult = { mockValue: "mock" };

let request: any;

// 使用msw模拟mock
server.use(
// rest是msw中用于restful接口的方法
rest.get(`${apiUrl}/${endpoint}`, (req, res, ctx) => {
request = req;
return res(ctx.json(mockResult));
})
);
// 使用http模块返回mock值
await http(endpoint, { token });
// 期望http的返回值和mock数据相等(注意不是完全相等,完全是toBe)
expect(request.headers.get("Authorization")).toBe(`Bearer ${token}`);
});

测试 Hook

新建__test__/use-async文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { act, renderHook } from "@testing-library/react";
import { useAsync } from "utils/use-async";

const defaultState: ReturnType<typeof useAsync> = {
stat: "idle",
error: null,
data: null,
isIdle: true,
isLoading: false,
isError: false,
isSuccess: false,
run: expect.any(Function),
setData: expect.any(Function),
setError: expect.any(Function),
retry: expect.any(Function),
};

const loadingState: ReturnType<typeof useAsync> = {
...defaultState,
stat: "loading",
isLoading: true,
isIdle: false,
};
const successState: ReturnType<typeof useAsync> = {
...defaultState,
stat: "success",
isSuccess: true,
isIdle: false,
};

// 测试脚本
test("useAsync可以异步处理", async () => {
let resolve: any, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});

const { result } = renderHook(() => useAsync());
// 期望Hook的默认返回值与test的默认值一致
expect(result.current).toEqual(defaultState);

let p: Promise<any>;
// 如果操作包含改变setState,需要使用act包裹
act(() => {
p = result.current.run(promise);
});
// 期望刚创建的promise中是loading状态
expect(result.current).toEqual(loadingState);

// 期望promise执行完毕后,返回的值是success状态的值
const resolvedValue = { mockValue: "resolved" };
act(async () => {
// 执行resolve,之后promise就是fulfilled
resolve(resolvedValue);
await p;
});
expect(result.current).toEqual({
...successState,
data: resolvedValue,
});
});

测试组件

新建__tests__/mark.tsx用于测试高亮组件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { renderHook } from "@testing-library/react-hooks";
import { screen } from "@testing-library/react";
import { Mark } from "../components/mark";

test("Mark组件正确高亮关键词", () => {
const name = "物料管理";
const keyword = "管理";

renderHook(() => <Mark name={name} keyword={keyword} />);

// 期望keyword存在document中
expect(screen.getByText(keyword)).toBeInTheDocument();
expect(screen.getByText(keyword)).toHaveStyle("color: #257AFD");
// 期望其他字没有高亮颜色
expect(screen.getByText("物料")).not.toHaveStyle("color: #257AFD");
});