30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

Enzymeのv3でshallowすると自動的にcomponentDidMountが呼ばれる

Reactのバージョンを16に上げるのに伴いEnzymeのバージョンを3にしたら、一部のテストが壊れた。
調べたところ、v3からshallowの挙動が変わったことが原因だった。
https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#lifecycle-methods

v2では、明示的に呼び出さない限りcomponentDidMountcomponentDidUpdateは実行されないが、v3ではshallowすると明示しなくても実行される。

v2の挙動

2.9.1で、挙動を確認してみる。

まず、テストの対象となるFooコンポーネントを作成した。

import React from 'react';

export default class Foo extends React.Component {
  constructor() {
    super();
    this.count = 0;
    this.history = [];
  }
  componentWillMount() {
    this.count = this.count + 1;
    this.history.push('componentWillMount');
  }
  componentDidMount() {
    this.count = this.count + 1;
    this.history.push('componentDidMount');
  }
  render() {
    this.count = this.count + 1;
    this.history.push('render');
    return <div>foo</div>;
  }
}

各メソッドが呼ばれる度にcountが増え、呼ばれたメソッドの名前がhistoryに追加されていく。

そのテストコードが以下。

import assert from 'assert';
import React from 'react';
import {shallow} from 'enzyme';

import Foo from '../Foo';

describe('Foo', () => {
  it('Lifecycle', () => {
    const wrapper = shallow(<Foo />);
    assert(wrapper.instance().count === 2);
    assert(wrapper.instance().history.length === 2);
    assert(wrapper.instance().history[0] === 'componentWillMount');
    assert(wrapper.instance().history[1] === 'render');
    wrapper.instance().componentDidMount();
    assert(wrapper.instance().count === 3);
    assert(wrapper.instance().history[2] === 'componentDidMount');
  });
});

shallowした段階では、メソッドが2回呼ばれている。
まずcomponentWillMount、そしてrender
componentDidMountは、明示的に呼び出さない限り実行されない。

v3の挙動

続いて、3.2.0での挙動を見てみる。

ちなみにv3からは、アダプターの設定が必要になる。

// テストのための設定ファイル
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';

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

v3で先程のテストを実行すると失敗する。
次のように書き換えることで、パスするようになる。

describe('Foo', () => {
  it('Lifecycle', () => {
    const wrapper = shallow(<Foo />);
    assert(wrapper.instance().count === 3);
    assert(wrapper.instance().history.length === 3);
    assert(wrapper.instance().history[0] === 'componentWillMount');
    assert(wrapper.instance().history[1] === 'render');
    assert(wrapper.instance().history[2] === 'componentDidMount');
    wrapper.instance().componentDidMount();
    assert(wrapper.instance().count === 4);
    assert(wrapper.instance().history[3] === 'componentDidMount');
  });
});

shallowの時点でcomponentDidMountが呼びされており、wrapper.instance().componentDidMount();とするとさらにもう一度呼ばれていることが分かる。

対策

v2と同じ挙動にしたい場合、shallowを次のように書けばいい。

const wrapper = shallow(<Foo />, {disableLifecycleMethods: true});

テスト全体に適用させたい場合は、テストの設定ファイルなどに{disableLifecycleMethods: true}を書けばいい。
つまり、こうなる。

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