使用 Enzyme 进行单元测试

¥Unit Testing with Enzyme

Airbnb 的 Enzyme 是一个用于为 React 组件编写测试的库。它使用 "adapters" 支持不同版本的 React 和类 React 库。Preact 有一个适配器,由 Preact 团队维护。

¥Airbnb's Enzyme is a library for writing tests for React components. It supports different versions of React and React-like libraries using "adapters". There is an adapter for Preact, maintained by the Preact team.

Enzyme 支持使用 Karma 等工具在普通或无头浏览器中运行的测试,或使用 jsdom 作为浏览器 API 的虚假实现在 Node 中运行的测试。

¥Enzyme supports tests that run in a normal or headless browser using a tool such as Karma or tests that run in Node using jsdom as a fake implementation of browser APIs.

有关使用 Enzyme 的详细介绍和 API 参考,请参阅 Enzyme 文档。本指南的其余部分解释了如何使用 Preact 设置 Enzyme,以及 Enzyme with Preact 与 Enzyme with React 的不同之处。

¥For a detailed introduction to using Enzyme and an API reference, see the Enzyme documentation. The remainder of this guide explains how to set Enzyme up with Preact, as well as ways in which Enzyme with Preact differs from Enzyme with React.



安装

¥Installation

使用以下命令安装 Enzyme 和 Preact 适配器:

¥Install Enzyme and the Preact adapter using:

npm install --save-dev enzyme enzyme-adapter-preact-pure

配置

¥Configuration

在测试设置代码中,你需要配置 Enzyme 以使用 Preact 适配器:

¥In your test setup code, you'll need to configure Enzyme to use the Preact adapter:

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-preact-pure';

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

有关将 Enzyme 与不同测试运行程序一起使用的指南,请参阅 Enzyme 文档的 指南 部分。

¥For guidance on using Enzyme with different test runners, see the Guides section of the Enzyme documentation.

示例

¥Example

假设我们有一个简单的 Counter 组件,它显示初始值,并带有一个用于更新它的按钮:

¥Suppose we have a simple Counter component which displays an initial value, with a button to update it:

import { h } from 'preact';
import { useState } from 'preact/hooks';

export default function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  const increment = () => setCount(count + 1);

  return (
    <div>
      Current value: {count}
      <button onClick={increment}>Increment</button>
    </div>
  );
}

使用 mocha 或 Jest 等测试运行程序,你可以编写一个测试来检查它是否按预期工作:

¥Using a test runner such as mocha or Jest, you can write a test to check that it works as expected:

import { expect } from 'chai';
import { h } from 'preact';
import { mount } from 'enzyme';

import Counter from '../src/Counter';

describe('Counter', () => {
  it('should display initial count', () => {
    const wrapper = mount(<Counter initialCount={5}/>);
    expect(wrapper.text()).to.include('Current value: 5');
  });

  it('should increment after "Increment" button is clicked', () => {
    const wrapper = mount(<Counter initialCount={5}/>);

    wrapper.find('button').simulate('click');

    expect(wrapper.text()).to.include('Current value: 6');
  });
});

有关此项目的可运行版本和其他示例,请参阅 Preact 适配器存储库中的 例子/ 目录。

¥For a runnable version of this project and other examples, see the examples/ directory in the Preact adapter's repository.

Enzyme 的工作原理

¥How Enzyme works

Enzyme 使用已配置的适配器库来渲染组件及其子组件。然后,适配器将输出转换为标准化内部表示形式 ("反应标准树")。然后 Enzyme 用一个对象封装它,该对象具有查询输出和触发更新的方法。封装器对象的 API 使用类似 CSS 的 selectors 来定位部分输出。

¥Enzyme uses the adapter library it has been configured with to render a component and its children. The adapter then converts the output to a standardized internal representation (a "React Standard Tree"). Enzyme then wraps this with an object that has methods to query the output and trigger updates. The wrapper object's API uses CSS-like selectors to locate parts of the output.

全渲染、浅渲染和字符串渲染

¥Full, shallow and string rendering

Enzyme 有 3 个渲染 "modes":

¥Enzyme has three rendering "modes":

import { mount, shallow, render } from 'enzyme';

// Render the full component tree:
const wrapper = mount(<MyComponent prop="value"/>);

// Render only `MyComponent`'s direct output (ie. "mock" child components
// to render only as placeholders):
const wrapper = shallow(<MyComponent prop="value"/>);

// Render the full component tree to an HTML string, and parse the result:
const wrapper = render(<MyComponent prop="value"/>);
  • mount 函数以与在浏览器中渲染组件相同的方式渲染组件及其所有后代。

    ¥The mount function renders the component and all of its descendants in the same way they would be rendered in the browser.

  • shallow 函数仅渲染组件直接输出的 DOM 节点。所有子组件都会被替换为仅输出其子组件的占位符。

    ¥The shallow function renders only the DOM nodes that are directly output by the component. Any child components are replaced with placeholders that output just their children.

    这种模式的优点是,你可以为组件编写测试,而无需依赖子组件的详细信息,也无需构建其所有依赖。

    ¥The advantage of this mode is that you can write tests for components without depending on the details of child components and needing to construct all of their dependencies.

    与 React 相比,shallow 渲染模式在 Preact 适配器内部的工作方式有所不同。有关详细信息,请参阅下面的差异部分。

    ¥The shallow rendering mode works differently internally with the Preact adapter compared to React. See the Differences section below for details.

  • render 函数(不要与 Preact 的 render 函数混淆!)将组件渲染为 HTML 字符串。这对于测试服务器上渲染的输出或渲染组件而不触发其任何效果非常有用。

    ¥The render function (not to be confused with Preact's render function!) renders a component to an HTML string. This is useful for testing the output of rendering on the server, or rendering a component without triggering any of its effects.

使用 act 触发状态更新和效果

¥Triggering state updates and effects with act

在前面的示例中,.simulate('click') 用于单击按钮。

¥In the previous example, .simulate('click') was used to click on a button.

Enzyme 知道对 simulate 的调用可能会更改组件的状态或触发效果,因此它将在 simulate 返回之前立即应用任何状态更新或效果。当最初使用 mountshallow 渲染组件以及使用 setProps 更新组件时,Enzyme 会执行相同的操作。

¥Enzyme knows that calls to simulate are likely to change the state of a component or trigger effects, so it will apply any state updates or effects immediately before simulate returns. Enzyme does the same when the component is rendered initially using mount or shallow and when a component is updated using setProps.

然而,如果事件发生在 Enzyme 方法调用之外,例如直接调用事件处理程序(例如按钮的 onClick 属性),则 Enzyme 将不会意识到该更改。在这种情况下,你的测试将需要触发状态更新和效果的执行,然后要求 Enzyme 刷新其输出视图。

¥If however an event happens outside of an Enzyme method call, such as directly calling an event handler (eg. the button's onClick prop), then Enzyme will not be aware of the change. In this case, your test will need to trigger execution of state updates and effects and then ask Enzyme to refresh its view of the output.

  • 要同步执行状态更新和效果,请使用 preact/test-utils 中的 act 函数来封装触发更新的代码

    ¥To execute state updates and effects synchronously, use the act function from preact/test-utils to wrap the code that triggers the updates

  • 要更新 Enzyme 的渲染输出视图,请使用封装器的 .update() 方法

    ¥To update Enzyme's view of rendered output use the wrapper's .update() method

例如,下面是用于递增计数器的测试的不同版本,修改为直接调用按钮的 onClick 属性,而不是通过 simulate 方法:

¥For example, here is a different version of the test for incrementing the counter, modified to call the button's onClick prop directly, instead of going through the simulate method:

import { act } from 'preact/test-utils';
it('should increment after "Increment" button is clicked', () => {
    const wrapper = mount(<Counter initialCount={5}/>);
    const onClick = wrapper.find('button').props().onClick;

    act(() => {
      // Invoke the button's click handler, but this time directly, instead of
      // via an Enzyme API
      onClick();
    });
    // Refresh Enzyme's view of the output
    wrapper.update();

    expect(wrapper.text()).to.include('Current value: 6');
});

与 Enzyme 和 React 的区别

¥Differences from Enzyme with React

总体意图是使用 Enzyme + React 编写的测试可以轻松地与 Enzyme + Preact 一起使用,反之亦然。如果你需要将最初为 Preact 编写的组件切换为与 React 一起使用,则无需重写所有测试,反之亦然。

¥The general intent is that tests written using Enzyme + React can be easily made to work with Enzyme + Preact or vice-versa. This avoids the need to rewrite all of your tests if you need to switch a component initially written for Preact to work with React or vice-versa.

然而,需要注意的是,该适配器与 Enzyme 的 React 适配器之间的行为存在一些差异:

¥However there are some differences in behavior between this adapter and Enzyme's React adapters to be aware of:

  • "shallow" 渲染模式在底层的工作方式有所不同。它与 React 一致,仅渲染组件 "深一层",但与 React 不同的是,它创建真正的 DOM 节点。它还运行所有正常的生命周期钩子和效果。

    ¥The "shallow" rendering mode works differently under the hood. It is consistent with React in only rendering a component "one level deep" but, unlike React, it creates real DOM nodes. It also runs all of the normal lifecycle hooks and effects.

  • simulate 方法调度实际的 DOM 事件,而在 React 适配器中,simulate 只是调用 on<EventName> 属性

    ¥The simulate method dispatches actual DOM events, whereas in the React adapters, simulate just calls the on<EventName> prop

  • 在 Preact 中,状态更新(例如,在调用 setState 之后)会批量一起并异步应用。在 React 状态下,更新可以根据上下文立即应用或批量应用。为了使编写测试变得更容易,Preact 适配器在通过适配器上的 setPropssimulate 调用触发初始渲染和更新后刷新状态更新和效果。当通过其他方式触发状态更新或效果时,你的测试代码可能需要使用 preact/test-utils 包中的 act 手动触发效果和状态更新的刷新。

    ¥In Preact, state updates (eg. after a call to setState) are batched together and applied asynchronously. In React state updates can be applied immediately or batched depending on the context. To make writing tests easier, the Preact adapter flushes state updates and effects after initial renders and updates triggered via setProps or simulate calls on an adapter. When state updates or effects are triggered by other means, your test code may need to manually trigger flushing of effects and state updates using act from the preact/test-utils package.

详细信息请参见 Preact 适配器的自述文件

¥For further details, see the Preact adapter's README.

Preact 中文网 - 粤ICP备13048890号