React Hooks:使用 Enzyme 测试自定义 Hooks
你将学到什么
TL;DR - 将你的自定义钩子封装在一个组件中,并进行浅渲染以测试实现细节。
你将学到什么
- React 测试策略
- 用户可观察行为
- 实施细节
- 使用 Enzyme 测试自定义钩子
测试策略
测试 React 代码库大致有两种策略。
- 测试用户可观察行为
- 测试实施细节
测试用户可观察行为
测试用户可观察行为意味着编写针对组件的测试。
- 组件的渲染方式
- 当用户与 DOM 交互时,组件如何重新渲染。
- props/state 如何控制渲染内容
请考虑以下组件 -Greet
function Greet({ user = 'User' }) {
const [name, setName] = React.useState(user);
return <div onClick={() => setName('Pinocchio')}>Hello, {name}!</div>;
}
测试用户可观察行为Greet意味着
- 测试
Greet渲染是否流畅不崩溃。 - 测试
Hello, User!当未传递用户属性时是否渲染。 - 测试当将值传递给prop时是否
Hello, Bruce!渲染成功Bruceuser Hello, Pinocchio!测试当用户点击元素时文本是否会发生变化。
测试实施细节
测试实现细节意味着编写针对状态逻辑的测试。
- 如何使用默认值/属性值初始化状态
- 当处理程序被调用时,状态如何变化?
考虑同一个组件——Greet
function Greet({ user = 'User' }) {
const [name, setName] = React.useState(user);
return <div onClick={() => setName('Pinocchio')}>Hello, {name}!</div>;
}
测试实现细节Greet意味着
- 当未向用户传递属性时,测试是否
name设置为默认值UserGreet name当用户属性传递给某个对象时,测试是否将其设置为属性值。Greet- 测试调用
name时是否已更新。setName
使用 Enzyme 测试自定义钩子
注意:请确保您的 React 版本为 10.1 ^16.8.5。之前的版本中,Hooks 无法通过 Enzyme 的浅渲染重新渲染组件,React 团队已在此版本中修复了此问题。如果您的 React 版本低于 10.1,您可能需要.update()在每次更改后使用 Enzyme 的 mount 和您的包装器来测试重新渲染。
当你在为包含展示(UI)逻辑并向 DOM 渲染元素的组件编写测试时,测试实现细节似乎没有必要,甚至可能被认为是一种不好的做法。但是,自定义 Hook只包含状态逻辑,因此我们必须彻底测试其实现细节,以便准确了解自定义 Hook 在组件中的行为。
让我们编写一个自定义钩子来更新和验证表单字段。
/* useFormField.js */
import React from 'react';
function useFormField(initialVal = '') {
const [val, setVal] = React.useState(initialVal);
const [isValid, setValid] = React.useState(true);
function onChange(e) {
setVal(e.target.value);
if (!e.target.value) {
setValid(false);
} else if (!isValid) setValid(true);
}
return [val, onChange, isValid];
}
export default useFormField;
自定义 Hook 功能虽然强大,能够抽象出代码中可重用的逻辑,但它们也存在一个局限性。尽管它们只是 JavaScript 函数,却只能在 React 组件内部运行。你不能直接调用它们并针对 Hook 的返回值编写测试。你必须将它们封装在一个 React 组件中,然后测试组件返回的值。
- 自定义钩子函数无法像 JavaScript 函数那样进行测试。
- 自定义 Hook 应该封装在 React 组件中以测试其行为
由于 Hook 具有可组合性,我们可以将 Hook 作为 prop 传递给组件,一切都会按预期运行。我们还可以编写一个包装组件来渲染和测试我们的 Hook。
/* useFormField.test.js */
function HookWrapper(props) {
const hook = props.hook ? props.hook() : undefined;
return <div hook={hook} />;
}
现在我们可以像访问 JavaScript 对象一样访问这个钩子,并测试它的行为。
/* useFormField.test.js */
import React from 'react';
import { shallow } from 'enzyme';
import useFormField from './useFormField';
function HookWrapper(props) {
const hook = props.hook ? props.hook() : undefined;
return <div hook={hook} />;
}
it('should set init value', () => {
let wrapper = shallow(<HookWrapper hook={() => useFormField('')} />);
let { hook } = wrapper.find('div').props();
let [val, onChange, isValid] = hook;
expect(val).toEqual('');
wrapper = shallow(<HookWrapper hook={() => useFormField('marco')} />);
// destructuring objects - {} should be inside brackets - () to avoid syntax error
({ hook } = wrapper.find('div').props());
[val, onChange, isValid] = hook;
expect(val).toEqual('marco');
});
自定义钩子的完整测试套件useFormField如下所示。
/* useFormField.test.js */
import React from 'react';
import { shallow } from 'enzyme';
import useFormField from './useFormField';
function HookWrapper(props) {
const hook = props.hook ? props.hook() : undefined;
return <div hook={hook} />;
}
describe('useFormField', () => {
it('should render', () => {
let wrapper = shallow(<HookWrapper />);
expect(wrapper.exists()).toBeTruthy();
});
it('should set init value', () => {
let wrapper = shallow(<HookWrapper hook={() => useFormField('')} />);
let { hook } = wrapper.find('div').props();
let [val, onChange, isValid] = hook;
expect(val).toEqual('');
wrapper = shallow(<HookWrapper hook={() => useFormField('marco')} />);
// destructuring objects - {} should be inside brackets - () to avoid syntax error
({ hook } = wrapper.find('div').props());
[val, onChange, isValid] = hook;
expect(val).toEqual('marco');
});
it('should set the right val value', () => {
let wrapper = shallow(<HookWrapper hook={() => useFormField('marco')} />);
let { hook } = wrapper.find('div').props();
let [val, onChange, isValid] = hook;
expect(val).toEqual('marco');
onChange({ target: { value: 'polo' } });
({ hook } = wrapper.find('div').props());
[val, onChange, isValid] = hook;
expect(val).toEqual('polo');
});
it('should set the right isValid value', () => {
let wrapper = shallow(<HookWrapper hook={() => useFormField('marco')} />);
let { hook } = wrapper.find('div').props();
let [val, onChange, isValid] = hook;
expect(val).toEqual('marco');
expect(isValid).toEqual(true);
onChange({ target: { value: 'polo' } });
({ hook } = wrapper.find('div').props());
[val, onChange, isValid] = hook;
expect(val).toEqual('polo');
expect(isValid).toEqual(true);
onChange({ target: { value: '' } });
({ hook } = wrapper.find('div').props());
[val, onChange, isValid] = hook;
expect(val).toEqual('');
expect(isValid).toEqual(false);
});
});
渲染自定义钩子并将其作为属性访问,应该可以让我们完全访问其返回值。
如果你在自定义 hook 中使用useEffecthook,请确保用 ` ReactTestUtils.act()shallow ` 包裹或mount调用 `request` ,以便在断言之前清除 effects。Enzyme 可能很快会在内部支持此功能,但目前这是必需的。更多信息请参见hooks-faq。
act(() => {
wrapper = shallow(<HookWrapper />);
});
本文中的所有代码片段都可以在仓库testing-hooks中找到,其中包含一个可运行的示例。
测试顺利!🎉
文章来源:https://dev.to/flexdinesh/react-hooks-test-custom-hooks-with-enzyme-40ib