学习内容来源:React + React Hook + TS 最佳实践-慕课网
相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:
项 | 版本 |
---|---|
react & react-dom | ^18.2.0 |
react-router & react-router-dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
husky | ^8.0.3 |
lint-staged | ^13.1.2 |
prettier | 2.8.4 |
json-server | 0.17.2 |
craco-less | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
react-helmet | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
react-query | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
具体配置、操作和内容会有差异,“坑”也会有所不同。。。
- 一、项目起航:项目初始化与配置
- 二、React 与 Hook 应用:实现项目列表
- 三、 TS 应用:JS神助攻 - 强类型
- 四、 JWT、用户认证与异步请求(上)
- 四、 JWT、用户认证与异步请求(下)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下)
- 六、用户体验优化 - 加载中和错误状态处理(上)
- 六、用户体验优化 - 加载中和错误状态处理(中)
- 六、用户体验优化 - 加载中和错误状态处理(下)
- 七、Hook,路由,与 URL 状态管理(上)
- 七、Hook,路由,与 URL 状态管理(中)
- 七、Hook,路由,与 URL 状态管理(下)
- 八、用户选择器与项目编辑功能(上)
- 八、用户选择器与项目编辑功能(下)
- 九、深入React 状态管理与Redux机制(一)
- 九、深入React 状态管理与Redux机制(二)
- 九、深入React 状态管理与Redux机制(三)
- 九、深入React 状态管理与Redux机制(四)
- 九、深入React 状态管理与Redux机制(五)
- 十、用 react-query 获取数据,管理缓存(上)
- 十、用 react-query 获取数据,管理缓存(下)
- 十一、看板页面及任务组页面开发(一)
- 十一、看板页面及任务组页面开发(二)
- 十一、看板页面及任务组页面开发(三)
- 十一、看板页面及任务组页面开发(四)
- 十一、看板页面及任务组页面开发(五)
- 十一、看板页面及任务组页面开发(六)
目的
防止出现“新代码破坏旧代码”的无限循环,让开发过程不再战战兢兢。
分类
单元测试:传统单元测试、组件测试、hook测试
集成测试:模块级别
e2e测试(end):页面级别
之前初始化项目的时候,默认安装了几个相关依赖:
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
再补充几个:
npm i @testing-library/react-hooks msw -D # --force
单元测试需要隔离环境,因此需要使用 msw 做 mock 使用
接下来写一个单元测试:
新建 src\__tests__\http.ts
(用来测试 src\utils\http.ts
)
import { setupServer } from "msw/node";
import { rest } from "msw";
import { http } from "utils/http";
const apiUrl = process.env.REACT_APP_API_URL;
const server = setupServer();
// jest 是对react最友好的一个测试库
// beforeAll 代表执行所有的测试之前,先来执行一下回调函数
beforeAll(() => server.listen());
// 每一个测试跑完以后,都重置mock路由
afterEach(() => server.resetHandlers());
// 所有的测试跑完后,关闭mock路由
afterAll(() => server.close());
test("http方法发送异步请求", async () => {
const endpoint = "test-endpoint";
const mockResult = { mockValue: "mock" };
server.use(
rest.get(`${apiUrl}/${endpoint}`, (req, res, ctx) =>
res(ctx.json(mockResult))
)
);
const result = await http(endpoint);
expect(result).toEqual(mockResult);
});
test("http请求时会在header里带上token", async () => {
const token = "FAKE_TOKEN";
const endpoint = "test-endpoint";
const mockResult = { mockValue: "mock" };
let request: any;
server.use(
rest.get(`${apiUrl}/${endpoint}`, async (req, res, ctx) => {
request = req;
return res(ctx.json(mockResult));
})
);
await http(endpoint, { token });
expect(request.headers.get("Authorization")).toBe(`Bearer ${token}`);
});
执行 npm run test
, 启动单元测试, 执行结果如下:
PASS src/__tests__/http.ts (5.495 s)
√ http方法发送异步请求 (57 ms)
√ http请求时会在header里带上token (7 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 7.61 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
新建 src\__tests__\use-async.ts
import { useAsync } from "utils/use-async";
import { act, renderHook } from "@testing-library/react-hooks";
const defaultState: ReturnType<typeof useAsync> = {
stat: "idle",
data: null,
error: 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",
isIdle: false,
isLoading: true,
};
const successState: ReturnType<typeof useAsync> = {
...defaultState,
stat: "success",
isIdle: false,
isSuccess: true,
};
test("useAsync 可以异步处理", async () => {
let resolve: any, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
const { result } = renderHook(() => useAsync());
expect(result.current).toEqual(defaultState);
let p: Promise<any>;
act(() => {
p = result.current.run(promise);
});
expect(result.current).toEqual(loadingState);
const resolvedValue = { mockedValue: "resolved" };
await act(async () => {
resolve(resolvedValue);
await p;
});
expect(result.current).toEqual({
...successState,
data: resolvedValue,
});
});
新建 src\__tests__\mark.tsx
:
import React from "react";
import { render, screen } from "@testing-library/react";
import { Mark } from "components/mark";
test("Mark 组件正确高亮关键词", () => {
const name = "物料管理";
const keyword = "管理";
render(<Mark name={name} keyword={keyword} />);
expect(screen.getByText(keyword)).toBeInTheDocument();
expect(screen.getByText(keyword)).toHaveStyle("color: #257AFD");
expect(screen.getByText("物料")).not.toHaveStyle("color: #257AFD");
});
部分引用笔记还在草稿阶段,敬请期待。。。