Enzyme测试实战:从零构建React组件测试体系

Enzyme测试实战:从零构建React组件测试体系

【免费下载链接】enzyme JavaScript Testing utilities for React 【免费下载链接】enzyme 项目地址: https://gitcode.com/gh_mirrors/en/enzyme

你是否还在为React组件测试而烦恼?是否觉得组件交互逻辑难以验证?本文将带你从零开始,使用Enzyme构建一套完整的React组件测试体系,让你轻松应对各种测试场景。读完本文,你将能够:

  • 正确安装和配置Enzyme测试环境
  • 掌握浅渲染、完全渲染和静态渲染三种测试方式
  • 熟练运用Enzyme API测试组件属性、状态和交互
  • 解决实际项目中常见的测试难题

什么是Enzyme?

Enzyme是一个用于React的JavaScript测试工具,它让测试React组件的输出变得更加简单。你还可以操作、遍历组件输出,甚至在一定程度上模拟运行时环境。Enzyme的API旨在直观且灵活,模仿jQuery的DOM操作和遍历API,让开发者能够轻松上手。

官方文档:README.md

环境准备与安装

安装Enzyme核心包

首先,我们需要安装Enzyme核心包。使用npm进行安装:

npm i --save-dev enzyme

Enzyme对测试运行器和断言库没有特殊要求,应该与所有主流测试运行器和断言库兼容。文档中的示例使用MochaChai,但你可以根据自己的喜好选择合适的工具。

安装文档:docs/installation/README.md

安装适配器

使用Enzyme时,除了核心包外,还需要安装与你使用的React版本相对应的适配器(Adapter)。例如,如果你使用的是React 16,可以运行:

npm i --save-dev enzyme-adapter-react-16

每个适配器可能有额外的peer依赖,你也需要一并安装。例如,enzyme-adapter-react-16需要reactreact-dom作为peer依赖。

目前,Enzyme提供的适配器及其与React的兼容性如下:

Enzyme适配器包React版本兼容性
enzyme-adapter-react-16^16.4.0-0
enzyme-adapter-react-16.3~16.3.0-0
enzyme-adapter-react-16.2~16.2
enzyme-adapter-react-16.1~16.0.0-0 || ~16.1
enzyme-adapter-react-15^15.5.0
enzyme-adapter-react-15.415.0.0-0 - 15.4.x
enzyme-adapter-react-14^0.14.0
enzyme-adapter-react-13^0.13.0

适配器源码:packages/

配置Enzyme

安装完成后,需要配置Enzyme以使用你选择的适配器。可以使用顶层的configure(...) API来完成配置:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

配置文件示例:packages/enzyme-example-mocha/src/Foo.spec.jsx

Enzyme三种渲染方式

Enzyme提供了三种主要的渲染方式,分别适用于不同的测试场景。

浅渲染(Shallow Rendering)

浅渲染是指将一个组件渲染成虚拟DOM对象,但只渲染该组件本身,不渲染其子组件。这使得测试组件时不需要关心子组件的实现细节,非常适合单元测试。

使用方法:

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';

import MyComponent from './MyComponent';
import Foo from './Foo';

describe('<MyComponent />', () => {
  it('renders three <Foo /> components', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find(Foo)).to.have.lengthOf(3);
  });

  it('renders an `.icon-star`', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find('.icon-star')).to.have.lengthOf(1);
  });

  it('renders children when passed in', () => {
    const wrapper = shallow((
      <MyComponent>
        <div className="unique" />
      </MyComponent>
    ));
    expect(wrapper.contains(<div className="unique" />)).to.equal(true);
  });

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = shallow(<Foo onButtonClick={onButtonClick} />);
    wrapper.find('button').simulate('click');
    expect(onButtonClick).to.have.property('callCount', 1);
  });
});

浅渲染API文档:docs/api/shallow.md

浅渲染实现:packages/enzyme/src/shallow.js

完全DOM渲染(Full DOM Rendering)

完全DOM渲染会将组件渲染到一个真实的DOM节点中,包括所有子组件。这使得测试组件的生命周期方法、DOM交互等更加方便。

使用方法:

import React from 'react';
import sinon from 'sinon';
import { expect } from 'chai';
import { mount } from 'enzyme';

import Foo from './Foo';

describe('<Foo />', () => {
  it('allows us to set props', () => {
    const wrapper = mount(<Foo bar="baz" />);
    expect(wrapper.props().bar).to.equal('baz');
    wrapper.setProps({ bar: 'foo' });
    expect(wrapper.props().bar).to.equal('foo');
  });

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = mount((
      <Foo onButtonClick={onButtonClick} />
    ));
    wrapper.find('button').simulate('click');
    expect(onButtonClick).to.have.property('callCount', 1);
  });

  it('calls componentDidMount', () => {
    sinon.spy(Foo.prototype, 'componentDidMount');
    const wrapper = mount(<Foo />);
    expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
    Foo.prototype.componentDidMount.restore();
  });
});

完全渲染API文档:docs/api/mount.md

完全渲染实现:packages/enzyme/src/mount.js

静态渲染(Static Rendered Markup)

静态渲染会将组件渲染成静态的HTML字符串,然后使用Cheerio库对其进行解析。这种方式速度快,适合测试组件的渲染输出是否符合预期。

使用方法:

import React from 'react';
import { expect } from 'chai';
import { render } from 'enzyme';

import Foo from './Foo';

describe('<Foo />', () => {
  it('renders three `.foo-bar`s', () => {
    const wrapper = render(<Foo />);
    expect(wrapper.find('.foo-bar')).to.have.lengthOf(3);
  });

  it('renders the title', () => {
    const wrapper = render(<Foo title="unique" />);
    expect(wrapper.text()).to.contain('unique');
  });
});

静态渲染API文档:docs/api/render.md

静态渲染实现:packages/enzyme/src/render.js

Enzyme常用API

Enzyme提供了丰富的API,用于查询、操作和断言组件。下面介绍一些最常用的API。

选择器(Selectors)

Enzyme支持多种选择器,用于查找组件或DOM元素:

  • CSS选择器:wrapper.find('.class')wrapper.find('#id')wrapper.find('tag')
  • 组件构造函数:wrapper.find(Foo)
  • 组件显示名称:wrapper.find('Foo')
  • 对象属性选择器:wrapper.find({ prop: 'value' })

选择器文档:docs/api/selector.md

组件属性和状态

Enzyme提供了方便的方法来获取和设置组件的属性和状态:

  • props():获取组件的所有属性
  • prop(key):获取指定属性的值
  • state():获取组件的状态
  • setProps(props):设置组件的属性
  • setState(state):设置组件的状态

属性操作API:docs/api/ReactWrapper/prop.md

状态操作API:docs/api/ReactWrapper/state.md

事件模拟

Enzyme可以模拟用户事件,如点击、输入等:

  • simulate(event, ...args):模拟事件触发

事件模拟API:docs/api/ReactWrapper/simulate.md

组件遍历

Enzyme提供了多种方法来遍历组件树:

  • children():获取所有子组件
  • find(selector):查找符合选择器的组件
  • filter(selector):过滤符合选择器的组件
  • first():获取第一个匹配的组件
  • last():获取最后一个匹配的组件
  • at(index):获取指定索引的组件

遍历API文档:docs/api/ReactWrapper/children.md

实际应用场景

测试React Hooks

Enzyme支持React Hooks,但在.shallow()中存在一些限制,这是由于React浅渲染器的上游问题:

  • useEffect()useLayoutEffect()在React浅渲染器中不会被调用。相关问题
  • useCallback()在React浅渲染器中不会记忆回调函数。相关问题

如果使用React 16.8+和.mount(),Enzyme会将包括.simulate().setProps().setContext().invoke()等API用ReactTestUtils.act()包装,因此你不需要手动包装。

一个常见的使用.act()触发处理函数并断言的模式是:

const wrapper = mount(<SomeComponent />);
act(() => wrapper.prop('handler')());
wrapper.update();
expect(/* ... */);

由于在Enzyme内部用.act()包装.prop()(或.props())的结果会破坏返回值的相等性,因此我们不能这样做。但是,你可以使用.invoke()来简化代码:

const wrapper = mount(<SomeComponent />);
wrapper.invoke('handler')();
expect(/* ... */);

Hooks测试示例:packages/enzyme-test-suite/test/shared/hooks/

测试组件生命周期

Enzyme可以方便地测试组件的生命周期方法:

it('calls componentDidMount', () => {
  sinon.spy(Foo.prototype, 'componentDidMount');
  const wrapper = mount(<Foo />);
  expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
  Foo.prototype.componentDidMount.restore();
});

生命周期测试示例:packages/enzyme-test-suite/test/shared/lifecycles/

与测试框架集成

Enzyme可以与各种测试框架集成,如Mocha、Jest、Karma等。

Mocha示例项目:packages/enzyme-example-mocha/

常见问题与解决方案

问题1:浅渲染无法测试子组件

解决方案:如果需要测试子组件,可以使用dive()方法深入浅渲染的组件:

const wrapper = shallow(<ParentComponent />);
const childWrapper = wrapper.find(ChildComponent).dive();

dive() API文档:docs/api/ShallowWrapper/dive.md

问题2:模拟事件不触发预期行为

解决方案:确保正确选择元素并传递必要的参数:

// 错误
wrapper.find('button').simulate('click');

// 正确
wrapper.find('button').simulate('click', { preventDefault: () => {} });

事件模拟常见问题:docs/common-issues.md

问题3:测试异步代码

解决方案:使用async/await或返回Promise:

it('fetches data on mount', async () => {
  fetchMock.getOnce('/data', { data: 'test' });
  const wrapper = mount(<DataComponent />);
  await wrapper.instance().fetchData();
  expect(wrapper.state('data')).to.equal('test');
});

异步测试示例:packages/enzyme-test-suite/test/shared/lifecycles/componentDidMount.jsx

总结与展望

通过本文的介绍,你应该已经掌握了Enzyme的基本用法和高级技巧。Enzyme作为一个强大的React测试工具,能够帮助你编写更可靠、更易维护的组件测试。

Enzyme团队正在不断改进和完善这个工具,你可以通过Enzyme Future了解未来的发展计划。

如果你有兴趣为Enzyme做贡献,可以查看贡献指南

最后,如果你在使用Enzyme的过程中遇到任何问题,可以查阅官方文档或在社区寻求帮助。祝你测试愉快!

相关资源

【免费下载链接】enzyme JavaScript Testing utilities for React 【免费下载链接】enzyme 项目地址: https://gitcode.com/gh_mirrors/en/enzyme

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值