自React引入钩子函数以来,我们多了一个总结共享逻辑的选项——自定义钩子。阿里的ahooks是一个很棒的总结分享自定义钩子的例子。
问题也随之而来,怎么测试来确保它的稳定性?
你说它就是个函数,对于函数我们测它的输入输出不就好了嘛!
没毛病!然后我们就看到这样的错误:
钩子必须在一个React组建中运行。为了避免这个错误,我们要么把react和react-dom给mock了,要么设法包个傀儡组件用于测试。各种头疼。
幸好社区有解决方案——@tesing-library/react-hooks。假设我们有一个简单的useCounter()钩子函数:
import { useState, useCallback } from "react";
export default function useCounter() {
const [counter, setCounter] = useState(0);
const increase = useCallback(() => setCounter(counter + 1), [counter]);
return {
counter,
increase,
};
}
测试counter 状态的变化只需要两步:
renderHook()渲染一个测试用的组件,让钩子函数可以正常跑起来;- 在
act()方法中调用钩子函数提供的操作方法,让react重新渲染组件。
import useCounter from "./useCounter";
import { renderHook, act } from "@testing-library/react-hooks";
test("basic hook rendering", () => {
const { result } = renderHook(() => useCounter());
expect(result.current).toMatchInlineSnapshot(`
Object {
"counter": 0,
"increase": [Function],
}
`);
act(() => result.current.increase());
expect(result.current.counter).toBe(1);
});
然后如常断言测试。
如果遇到异步操作,则与《React组件单元测试之交互》一文中介绍的waitFor()方法雷同,需多加一步等待响应发生:
import useCounter from "./useCounter";
import { renderHook, act } from "@testing-library/react-hooks";
test("async hook rendering - waitFor", async () => {
const { result, waitFor } = renderHook(() => useCounter());
act(() => result.current.increase());
await waitFor(() => expect(result.current.counter).toBe(1));
});
test("async hook rendering - waitForNextUpdate", async () => {
const { result, waitForNextUpdate } = renderHook(() => useCounter());
act(() => result.current.increase());
await waitForNextUpdate();
expect(result.current.counter).toBe(1);
});
test("async hook rendering - waitForValueToChange", async () => {
const { result, waitForValueToChange } = renderHook(() => useCounter());
act(() => result.current.increase());
await waitForValueToChange(() => result.current.counter);
expect(result.current.counter).toBe(1);
});
以上例子中提供了3个选项:
- waitFor
- waitForNextUpdate
- waitForValueToChange
分别可以应用于不同的测试目标。
是不是非常简便?