单元测试是软件开发的基本实践,对于任何软件工程师来说尤为重要。
它涉及孤立地测试代码的单个单元或组件以确保它们按预期工作。
在React的背景下,这些单元通常是指应用程序代码库的各个功能,组件或小部分。
单位测试有两个重大收益
-
生活文档:单位测试是您代码库的生活文档的一种形式。单位测试不仅依靠评论或外部文档,而是提供了有关代码应如何行为的具体示例。只要您与代码一起维护测试,本文档将保持最新状态,在挂钩的情况下,这些测试用例将探索实现挂钩的不同方面。
-
预防回归:单位测试是未来变化的安全网。当您修改或扩展代码时,运行单元测试可以帮助您确保现有功能保持完整。如果您的任何更改都打破了预期的行为,测试将捕获它,从而使您可以在问题到生产之前解决问题。
执行
我们现在将创建一个具有历史功能的计数器钩。
在您的React应用程序中安装以下软件包
npm i use-state-with-history
如果您不使用Create-React-app,则需要安装
@testing-library/react
现在我们创建钩子
// src/use-counter.tsx
export function useCounterWithHistory(initialValue: number) {
const [count, setCount, { backward, forward, go, history }] = useStateWithHistory<number>(initialValue);
return [count, setCount, { backward, forward, go, history }];
}
我们现在喜欢测试案例:
// src/test/use-counter.test.tsx
import React from "react";
import { renderHook, screen } from "@testing-library/react";
import { useStateWithHistory } from "../use-counter";
import { act } from "react-dom/test-utils";
describe("Counter", () => {
test("mounts a count of 0", async () => {
const promise = Promise.resolve();
const { result } = renderHook(() => useStateWithHistory<number>(0));
expect(result.current[0]).toBe(0);
await act(() => promise);
});
test("mounts a count of 0 and increments by one", async () => {
const promise = Promise.resolve();
const { result } = renderHook(() => useStateWithHistory<number>(0));
act(() => result.current[1](result.current[0] + 1));
expect(result.current[0]).toBe(1);
await act(() => promise);
});
test("increments by one two times start with zero", async () => {
const promise = Promise.resolve();
const { result } = renderHook(() => useStateWithHistory<number>(0));
act(() => {
result.current[1](result.current[0] + 1);
});
act(() => {
result.current[1](result.current[0] + 1);
});
expect(result.current[0]).toBe(2);
await act(() => promise);
});
test("increments by one two times start with zero then goes back by 1 step", async () => {
const promise = Promise.resolve();
const { result } = renderHook(() => useStateWithHistory<number>(0));
act(() => {
result.current[1](result.current[0] + 1);
});
act(() => {
result.current[1](result.current[0] + 1);
});
act(() => {
result.current[2].backward();
});
expect(result.current[0]).toBe(1);
await act(() => promise);
});
test("increments by one two times start with zero then goes back by 2 steps and one forward", async () => {
const promise = Promise.resolve();
const { result } = renderHook(() => useStateWithHistory<number>(0));
act(() => {
result.current[1](result.current[0] + 1);
});
act(() => {
result.current[1](result.current[0] + 1);
});
act(() => {
result.current[2].backward();
});
act(() => {
result.current[2].backward();
});
act(() => {
result.current[2].forward();
});
expect(result.current[0]).toBe(1);
await act(() => promise);
});
test("increments by one two times start with zero then goes back by 2 steps and one forward then goes to 0", async () => {
const promise = Promise.resolve();
const { result } = renderHook(() => useStateWithHistory<number>(0));
act(() => {
result.current[1](result.current[0] + 1);
});
act(() => {
result.current[1](result.current[0] + 1);
});
act(() => {
result.current[2].backward();
});
act(() => {
result.current[2].backward();
});
act(() => {
result.current[2].forward();
});
act(() => {
result.current[2].go(0);
});
expect(result.current[0]).toBe(0);
await act(() => promise);
});
});
您可以看到,我们有很多测试用例,我们想测试挂钩的所有可能用例,这可以确保我们保留了单位案例的living documentation
目标。
还可以通过覆盖效果的情况进行任何更改,都可以确保它不会破坏现有的钩子案例。
注意:在嵌入事件的情况下,您需要将它们包装在
act
中。
result
对象是一个react ref,因此进行act
会突变钩的内部状态。
最后,您可以使用react-scripts
如果您使用混音,下一个或任何自定义模板,则可以安装它,但是如果使用Create-Rexct-App,它已经安装了,如果您有自己的自定义测试设置,则可以跳过此部分
npx react-scripts test