Enzyme というライブラリがあります。
React の描画をいい感じにラップして、コンポーネントをテストしやすくするライブラリです。
今回は ES6 の Proxy という新機能を使って、このライブラリに独自の API を生やすという趣向です。
Enzyme の基本機能については面倒なので説明しません。
mount と simulate と find さえ知っていればよいです。
問題設定
Enzyme の Github に、
const elm = mount(<SomeComponent />);
elm.simulate('click');
とかいちいち書くのは面倒だしわかりにくいんで、
elm.click()
と書かせてほしいという要望があがっていたので、それがきっかけです。
解法1: モンキーパッチ
mount が返すのは ReactWrapper というクラスのインスタンスです。
なので、その prototype に新しいメソッドを生やしてあげればよろしい。
ReactWrapper.prototype.click = function (mock) {
this.simulate('click', mock);
};
ですが、これは元の ReactWrapper を壊しかねず、いま動いても将来のバージョンが 0.1 上がるだけで動かなくなる可能性があります。
解法2: プロキシ
よって、もとの API を壊さずに機能を拡張したい。
そこで使えるのが ES6 で入った Proxy という言語機能です。
これは既存のオブジェクトをラップして必要に応じて割り込みをかけるものです。
これを使うと下のように mount と ReactWrapper を拡張できます。
import Enzyme from 'enzyme';
function mount() {
const element = Enzyme.mount.apply(this, arguments);
return new Proxy(element, {
'get': function (target, key) {
switch(key) {
case 'click': // Add
return this.simulate('click', ...arguments);
default:
return target[key];
}
}
});
}
……が、これだけでは実際には足りません。
子要素をたどる find という API を使うとエラーになってしまいます。
const elm = mount(<SomeComponent />);
const elm2 = elm.find('button');
elm2.click(); // ERROR! click is not a function
これは、find が元の ReactWrapper を返すから。
よって、以下のようにして、find もラップしてあげる必要があります。
import Enzyme from 'enzyme';
function mount() {
const element = Enzyme.mount.apply(this, arguments);
return new Proxy(element, {
'get': function (target, key) {
switch(key) {
case 'click':
return this.simulate('click', ...arguments);
case 'find': // Wrap
return function () {
const element2 = target.find.apply(target, arguments);
return createReactWrapperProxy(element2);
}
default:
return target[key];
}
}
});
}
これをあらゆる DOM 探索 API に対して記述すれば、目的のものが作れます。
まとめ
Proxy を用いると public API だけを用いて、もとのモジュールをまったく壊さずにそれを拡張したモジュールが作れます。たるいのでやりませんが。
参考
コードの全体はGithub においてあります。
Proxy の使い方についてはMDNにまとまっています。