問題意識
reactのコンポーネントのテストは、react-addon-test-utilsをそのまま使うと面倒なことが多いです。具体的には、
- コンポーネントを描画するためにdom環境が必要(jsdomやkarmaなどを使うのが一般的)
- 子コンポーネントも全て描画される
前者は、特にReact Nativeのテストをしたい時に困ります。dom環境ではないランタイムが必要でテストできないので。
後者は、単体テストの原則に反します。また、reduxのconnectなどを子コンポーネントに適用していた場合はテスト環境の構築も面倒になってきます。
そこで登場したのがenzymeです。
shallow renderingを用いることで、jsdomなどのランタイムなしに対象のコンポーネントのみをテストすることができます。
しかし、実際にコンポーネントをmountしているわけではないため、
- 実際に表示される文言のテストがしにくかった
- 一部のライフサイクル系の振る舞いは確認できなかった
この記事のゴール
2017現在では、上記2つの問題を解決できて良い感じにテストできるんだよ、というのを示したいと思います。
なお、React Nativeでも同じようにテストしたいため、jsdomなどに依存しない shallow rendering を前提とします。
(ぶっちゃけ、このPR見ればほぼわかるかと思います→https://github.com/airbnb/enzyme/pull/318 )
実際に表示される文言のテスト
(React Nativeの例ですが、普通のReactでも同様にテストできます。)
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.ios.js
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
</View>
);
}
}
describe('<Intro />', function () {
it('ReactNativeSample', function () {
const wrapper = shallow(<ReactNativeSample />);
assert(wrapper.find('Text').at(0).prop('children') === 'Welcome to React Native!');
assert(wrapper.find('Text').at(1).prop('children') === 'To get started, edit index.ios.js');
})
});
このように、実際に表示された文字に対してassertを掛けるのではなく、<Text>
タグなど文言の表示をするコンポーネントのpropsに渡ってくる文字列を確認することで、描画テストの代わりとみなせます。
wrapper.contains(hoge)
みたいなテストよりもこちらの方が詳細なテストを書けるのではないでしょうか?
(※ただし、<Text>hello {this.props.name}</Text>
のように書くと通らず、<Text>{`hello ${this.props.name}`}</Text>
のように書く必要がありました。)
一部のライフサイクル系の振る舞いは確認できなかった
文言から解ると思いますが、これは上記PRのおかげでもう過去の話です。今はできます。
it('componentDidMount', () => {
const spy = sinon.spy();
class Foo extends React.Component {
componentDidMount() {
spy();
}
render() {
return <div>foo</div>;
}
}
shallow(<Foo />, { lifecycleExperimental: true });
assert(spy.getCalls().length === 1);
});
it('componentWillUnmount', () => {
const spy = sinon.spy();
class Foo extends React.Component {
componentWillUnmount() {
spy();
}
render() {
return <div>foo</div>;
}
}
const wrapper = shallow(<Foo />);
wrapper.unmount();
assert(spy.getCalls().length === 1);
});
キモはこれです。{ lifecycleExperimental: true }
これにより、componentDidMountのような、これまでshallowでは再現できなかったライフサイクルメソッドも扱えるようになっています。
まとめ
いかがでしょうか。
reactを書くなら、実質enzymeは必須のように思います。
また、reduxのようなフレームワークをenzymeのshallowを駆使すると、AngularのDIの仕組みでしたかったことと同じことが出来るようになるのですが、それはまた別の話。