#jest
首先考虑一下,React 组件我们需要测试哪些东西呢?
接下来我们来考虑一下怎么去测试,Jest 提供了一个基于快照的测试方式,每当你想要确保你的UI不会有意外的改变,快照测试是非常有用的工具。
这里主要讲一下 React 提供的 react-test-render
和 react-dom/test-utils
,
import React from "react";
const ShallowRender = () => {
return (
<div>
<p>1111</p>
<ul>
<li>2222</li>
<li>3333</li>
<li>4444</li>
<li>5555</li>
</ul>
</div>
);
};
export default ShallowRender;
组件只会被 render 一层(children 中的 React 组件不会被 render)
我们可以看一下源码中 getRenderoutput
getRenderOutput() {
return this._rendered;
}
接着我们再来看看 在 render 方法中对this._rendered
的赋值
if (shouldRender) {
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = this._dispatcher;
try {
// elementType could still be a ForwardRef if it was
// nested inside Memo.
if (elementType.$$typeof === ForwardRef) {
if (!(typeof elementType.render === "function")) {
throw Error(
`forwardRef requires a render function but was given ${typeof elementType.render}.`
);
}
this._rendered = elementType.render.call(
undefined,
element.props,
element.ref
);
} else {
this._rendered = elementType(element.props, this._context);
}
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
可以看到 this._rendered 的值是elementType 的返回值,具体 elementType 干嘛可以自己看一下,我们可以看到这里只取了 element 的 type,这个 element就是我们传入的 组件,这里只取了这个组件的 type,并没有深入递归的去取 props 的 children。
import ShallowRenderer from "react-test-renderer/shallow";
import ShallowRender from "./shallowRende";
describe("react component", () => {
it("shallow render", () => {
const render = new ShallowRenderer();
render.render(<ShallowRender />);
const result = render.getRenderOutput();
console.log(result.props);
expect(result.type).toBe("div");
});
});
import TestRenderer from "react-test-renderer";
import ShallowRender from "./shallowRende";
describe("full render test", () => {
it("full test", () => {
const testRender = TestRenderer.create(<ShallowRender />);
const testInstance = testRender.root;
expect(testInstance.findAll((node) => node.type === "li")).toHaveLength(
4
);
});
});
对于 hooks 的单测一般使用的库是 @testing-library/react-hooks
,它提供了常用的方法有 renderHook
, act
renderHook: 渲染一个用于测试的组件,这个组件会调用包含 hook 的callback。
参数说明:
* callback (() => any) - 测试组件每次渲染时调用的方法。这个方法应该调用需要测试的 hook。
* options (object) - 配置对象。有以下配置项:
* initialProps (object) - 传递给callback函数的初始值
* wrapper (componenet) - pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating reusable custom render functions for common data providers
返回值:
* result (object)
* current (any) - callback函数返回的值
* error (Error) - callback函数抛出的错误。
* waitForNextUpdate (function) - 返回一个 Promise,它在下次组件渲染时结束。一般用于由于异步操作导致状态更新的情况。
* rerender (function([newProps])) - 重新渲染测试组件的方法。重新渲染测试组件时,会再次调用callback函数。如果指定了newProps,则会将newProps传递给callback。
* unmount (function()) - 卸载测试组件。一般用于触发useEffect hook 的清除动作。
Act: 帮我们调用一个方法
下面就以常见的 count 变化写一个 hooks
// useCounter.ts
import { useState } from "react";
const useCounter = () => {
const [count, setCount] = useState(0);
const addCount = () => {
setCount(count + 1);
};
const subCount = () => {
setCount(count - 1);
};
return { count, addCount, subCount };
};
export default useCounter;
他的单测试如下这样的
// useCounter.test.ts
import { renderHook, act } from "@testing-library/react-hooks";
import useCounter from "./useCounter";
describe("count hook", () => {
it("count hook test", () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.addCount();
});
expect(result.current.count).toBe(1);
act(() => {
result.current.subCount();
});
expect(result.current.count).toBe(0);
});
});