作为前端开发人员,单元测试是我们日常开发不可或缺的一部分。而针对 React 的单元测试,我们有一个非常强大的利器——Enzyme。Enzyme 是 AirBnb 开发的一个 React 测试工具,可以使我们的测试代码更简洁、易读以及易于维护。本文将针对 React 中的单元测试利器 Enzyme 做一个详细的入门指南,帮助读者了解如何使用 Enzyme 进行 React 单元测试。
Enzyme 概览
Enzyme 是一个 React 测试工具库,助我们更加容易地测试 React 组件。它提供了简单、灵活且易于维护的方式编写断言与查询。就像 jQuery 让我们更轻松地与 DOM 交互,Enzyme 也让我们更容易地与 React 组件进行交互。
与其他 JavaScript 测试工具不同,Enzyme 并不关心测试代码在浏览器还是在 Node.js 环境运行。因此,无论是 Karma、Jasmine、Mocha 还是 Jest,都与 Enzyme 兼容。
安装
首先,我们需要安装 Enzyme。Enzyme 可以通过 npm 包管理器轻松安装:
npm install --save-dev enzyme enzyme-adapter-react-16
接下来,我们需要配置 Enzyme 以兼容 React:
// index.js import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });
测试工具
Enzyme 提供了三个渲染工具供我们进行测试:
shallow
:用于测试组件的行为而不涉及其子组件,因为子组件的实现细节可能会影响父组件的测试结果。
import { shallow } from 'enzyme'; import MyComponent from './MyComponent'; describe('<MyComponent />', () => { it('renders properly', () => { const wrapper = shallow(<MyComponent />); expect(wrapper.find('.my-component')).toHaveLength(1); }); });
render
:用于静态 HTML 输出,我们可以用它来测试组件输出的 HTML 代码。
import { render } from 'enzyme'; import MyComponent from './MyComponent'; describe('<MyComponent />', () => { it('renders properly', () => { const wrapper = render(<MyComponent />); expect(wrapper.find('.my-component')).toHaveLength(1); }); });
mount
:用于测试组件的行为,包括其子组件,可以真实地渲染出组件所需的 DOM。
import { mount } from 'enzyme'; import MyComponent from './MyComponent'; describe('<MyComponent />', () => { it('renders properly', () => { const wrapper = mount(<MyComponent />); expect(wrapper.find('.my-component')).toHaveLength(1); }); });
Shallow 渲染和断言
在上面的小节中我们已经介绍了 shallow
渲染的作用。那么,我们来看一下它在实践中如何使用。
假设我们有一个 Button
组件,如下所示:
import React from 'react'; import PropTypes from 'prop-types'; const Button = ({ onClick, children }) => ( <button onClick={onClick}>{children}</button> ); Button.propTypes = { onClick: PropTypes.func, children: PropTypes.node, }; export default Button;
使用 shallow
渲染我们的 Button
组件:
import { shallow } from 'enzyme'; import Button from './Button'; describe('<Button />', () => { it('renders children when passed in', () => { const wrapper = shallow(<Button>Hello World!</Button>); expect(wrapper.contains('Hello World!')).toBe(true); }); it('simulates click events', () => { const mockFn = jest.fn(); const wrapper = shallow(<Button onClick={mockFn}>Click Me!</Button>); wrapper.find('button').simulate('click'); expect(mockFn).toHaveBeenCalled(); }); });
脚本中,我们通过 shallow
渲染了 Button
组件,接着我们通过 contains
函数来检验组件是否正确地包含了传入的字符串。同样的,我们使用了 Jest 的 mockFn()
函数来模拟一个函数,并在按钮被点击时调用这个函数,然后通过 toHaveBeenCalled
断言测试该函数是否被调用了。
Render 渲染和断言
render
渲染将 React 组件渲染成静态 HTML。因此,我们可以使用 render
渲染较为复杂的组件前端测试,例如有 Ajax 请求、redux 状态等的组件,以下为示例脚本:
import React from 'react'; import { render } from 'enzyme'; import MyComponent from './MyComponent'; describe('<MyComponent />', () => { it('renders its content', () => { const wrapper = render(<MyComponent />); expect(wrapper.find('.my-component')).toHaveLength(1); }); });
在这个例子中,我们使用 render
渲染了一个 MyComponent
组件。我们在查找 .my-component
元素的同时,检验组件是否正确地渲染出了我们预期的内容。
Mount 渲染和断言
mount
渲染是完整的渲染,它会考虑组件的子组件,还会考虑组件的真实宽高与动态操作影响的渲染,但使用起来成本较高。以下为示例脚本:
import React from 'react'; import { mount } from 'enzyme'; import MyComponent from './MyComponent'; describe('<MyComponent />', () => { it('renders correctly', () => { const wrapper = mount(<MyComponent />); expect(wrapper.find('.my-component')).toHaveLength(1); }); });
当我们使用 mount 渲染时,我们能够观察到组件在运行时的真实行为和 DOM 视图,因此我们可以通过 expect
接收其它回调函数来直接测试组件中的方法。例如:
import React from 'react'; import { mount } from 'enzyme'; import Form from './Form'; describe('<Form />', () => { it('Can be submitted', () => { const wrapper = mount(<Form onSubmit={() => {}} />); const handleSubmit = jest.spyOn(wrapper.instance(), 'handleSubmit'); wrapper.instance().forceUpdate(); // 手动强制更新 wrapper.find('button').simulate('submit'); expect(handleSubmit).toHaveBeenCalled(); }); });
使用 mount
渲染,我们可以直接测试组件的方法,或者观察一些实现的具体细节,从而进行更加真实的测试判断。
断言
Enzyme 支持所有的 Jest 的断言函数,在此就不多去赘述。在进行测试时,我们需要区分『测试元素』与『测试组件』的区别。对于不同类型的元素与组件,我们需要使用不同的 expect()
断言函数。例如:
expect(wrapper.text()).toEqual('Hello, World!')
测试text
元素expect(wrapper.find(MyComponent)).toHaveLength(1)
测试自定义组件expect(wrapper.find('.my-class')).toHaveLength(1)
测试元素类名(类名前面需要添加.
前缀)expect(wrapper.find('#my-id')).toHaveLength(1)
测试元素 ID(ID 前面需要添加#
前缀)
最佳实践
最后,我们总结一下 React 单元测试的最佳实践:
- 测试前准备好一份组件列表,并尽量使每个测试都能独立地进行,避免相互依赖导致测试出现问题。
- 尽量使用浅渲染,避免子组件的实现细节影响父组件测试结果。
- 在需要对方法执行的测试代码中,使用 mount 渲染,在方法执行时自动渲染空间进行自动验证。
最佳实践需要根据实际情况调整和执行,以上万维节介绍了一些最常见的方法来进行单元测试,其中的原理和使用教程也是您进行单元测试的宝贵参考资料。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6596d648eb4cecbf2da8e31d