帮助
支持我们

使用 Enzyme 进行单元测试

Airbnb 的 Enzyme 是一个用于编写 React 组件测试的库。它使用“适配器”支持不同版本的 React 和类似 React 的库。Preact 团队维护了一个 Preact 适配器。

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

有关使用 Enzyme 的详细介绍和 API 参考,请参阅 Enzyme 文档。本指南的其余部分将说明如何将 Enzyme 与 Preact 结合使用,以及 Enzyme 与 Preact 的不同之处和 Enzyme 与 React 的不同之处。



安装

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

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

配置

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

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

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

有关在不同的测试运行器中使用 Enzyme 的指南,请参阅 Enzyme 文档的指南部分。

示例

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

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 之类的测试运行器,您可以编写一个测试来检查它是否按预期工作

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 适配器存储库中的examples/目录。

Enzyme 的工作原理

Enzyme 使用它已配置的适配器库来呈现组件及其子组件。然后,适配器将输出转换为标准化的内部表示(“React 标准树”)。然后,Enzyme 用一个对象包装它,该对象具有查询输出和触发更新的方法。包装器对象的 API 使用类似 CSS 的选择器来定位输出的部分。

完整、浅层和字符串渲染

Enzyme 有三种渲染“模式”

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函数以与在浏览器中呈现相同的方式呈现组件及其所有后代。

  • shallow函数仅呈现组件直接输出的 DOM 节点。任何子组件都将替换为仅输出其子组件的占位符。

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

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

  • render函数(不要与 Preact 的render函数混淆!)将组件呈现为 HTML 字符串。这对于测试在服务器上呈现的输出或在不触发任何效果的情况下呈现组件很有用。

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

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

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

ただし、イベントが Enzyme メソッド呼び出しの外で発生した場合(ボタンの onClick プロップを直接呼び出すなど)、Enzyme は変更を認識しません。この場合、テストでは状態の更新とエフェクトの実行をトリガーしてから、Enzyme に出力のビューを更新するように要求する必要があります。

  • 状態の更新とエフェクトを同期的に実行するには、更新をトリガーするコードをラップするために preact/test-utils から act 関数を使用します
  • レンダリングされた出力の Enzyme のビューを更新するには、ラッパーの .update() メソッドを使用します

たとえば、カウンターを増分するためのテストの別のバージョンを次に示します。これは、simulate メソッドを使用するのではなく、ボタンの onClick プロップを直接呼び出すように変更されています

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 の違い

一般的な意図は、Enzyme + React を使用して記述されたテストを、Enzyme + Preact またはその逆で簡単に動作させることができるようにすることです。これにより、Preact 用に最初に記述されたコンポーネントを React で動作させる必要がある場合、またはその逆の場合に、すべてのテストを書き直す必要がなくなります。

ただし、このアダプターと Enzyme の React アダプターの動作には、注意すべき違いがいくつかあります

  • 「shallow」レンダリングモードは、内部的には異なって動作します。React ではコンポーネントを「1 レベルの深さ」でのみレンダリングするという点では一貫していますが、React とは異なり、実際の DOM ノードを作成します。また、通常のライフサイクルフックとエフェクトをすべて実行します。
  • simulate メソッドは実際の DOM イベントをディスパッチしますが、React アダプターでは simulateon<EventName> プロップを呼び出すだけです
  • Preact では、状態の更新(例: setState の呼び出し後)はまとめて非同期に適用されます。React では、状態の更新はコンテキストに応じてすぐに適用することも、バッチ処理することもできます。テストの記述を容易にするため、Preact アダプターは、アダプターでの setProps または simulate 呼び出しを介してトリガーされた初期レンダリングと更新後に状態の更新とエフェクトをフラッシュします。状態の更新またはエフェクトが他の手段でトリガーされた場合、テストコードは preact/test-utils パッケージから act を使用して、エフェクトと状態の更新のフラッシュを手動でトリガーする必要がある場合があります。

詳細については、Preact アダプターの READMEを参照してください。