種類と特徴
見づらくてすみません なるべく広げて見てください。
コンポーネントテスト | スナップショットテスト | メソッドテスト | |
---|---|---|---|
ツール の例 |
enzyme 任意のテストランナー 任意のアサーションライブラリ |
react-test-render jestなどの対応ツール |
任意のテストランナー 任意のアサーションライブラリ |
手法 | jqueryライクにDOMを操作 その際の変化や挙動を確認 |
正常なDOMツリーを出力保存 コード修正後の出力と比較し 意図しない変更がないか確認 |
メソッドを直接実行 挙動を確認 |
メリット | 特定の動きのテストができる | 作成、メンテコストが低い | メソッドのみでテストできる |
デメリット | ・テストコードが煩雑 ・作成、メンテコストが高い |
・"正常な状態" が最初に必要 ・エラー原因を見つけにくい |
・全てのメソッドを テストできる訳ではない |
可能な テスト |
イベント propsによる表示の変化 ライフサイクルメソッド 動き系メソッド(結合) 表示系メソッド(結合) - |
- propsによる表示の変化 - - 表示系メソッド(結合) - |
- - - - - 動き系メソッド(単体) |
向いて いる役割 |
コンポーネント内 結合テスト |
デグレーションテスト | 不安(≒複雑)なメソッドの 重点テスト |
enzymeの使い方
renderの種類
eg. shallowするときの記述
const props = { hoge : 1, fuga : 2 };
const wrapper = shallow(<Component {...props} />);
動きのシミュレーション実施例
クリックする
チェックボックス、ラジオボタンの選択でも使える
wrapper.find('[data-test=input]').simulate('click');
テキストボックスに入力する
値が挙動に関係ないのであれば、simulateの第二引数は無くてもok
wrapper.find('[data-test=input]').simulate('change', { target : { value : input }});
テスト実施例集
表示分岐のテスト
// テスト対象
const Component = (props) => (
<div>
{props.isHoge
? <span date-test="hoge">ほげ</span>
: <span date-test="fuga">ふが</span>
}
</div>
);
// 確認したいprops状態にして、shallowレンダリング
const props = { isHoge : true };
const wrapper = shallow(<Component {...props} />
// find + exists を使って、意図した表示になっているか確認
expect([
wrapper.find('[data-test=hoge]').exists(),
wrapper.find('[data-test=fuga]').exists(),
]).toEqual([
true,
false,
]);
クリックイベントのテスト
sinonも使います
// テスト対象
const Component = (props) => (
<div>
<button
data-test="btn"
onClick={() => props.hogeAction())
>
ボタンだよ
</button>
</div>
);
// テスト用props actionが実行されたかを見るため、sinon.spyを仕込む
const props = {
hogeAction : sinon.spy(),
};
// shallowレンダリング
const wrapper = shallow(<Component {...props} />);
// "ボタンを押す" 動きをシミュレートする
wrapper.find('[data-test=btn]').simulate('click');
// sinon.spyを通して、意図したアクションが呼ばれているか確認
expect(props.hogeAction.calledOnce).toEqual(true);
propsが変わった時のテスト
ライフサイクルメソッドのテストに使えます。
// テスト対象
class Component extends React.Component {
componentWillReceiveProps(nextProps) {
// false->trueに変わった時だけgetが呼ばれるので、この挙動をテストする
if (this.props.isHoge === false && nextProps.isHoge === true) {
this.props.getHoge();
}
}
// 他のメソッドは省略
}
// テスト用props 実行されたかを見るため sinon.spy を仕込む
const props = {
isHoge : false,
getHoge : sinon.spy(),
};
// shallowレンダリング
const wrapper = shallow(<Component {...props} />);
// propsを変更
wrapper.setProps({
...props,
isHoge : true,
});
// sinon.spyを通して呼ばれたことを確認
expect(props.getHoge.calledOnce).toEqual(true);
// propsを変更
wrapper.setProps({
...props,
isHoge : false,
});
// sinon.spyを通して呼ばれていないことを確認
expect(props.getHoge.calledTwice).toEqual(false);
※ 説明用にまとめて書いていますが、実際はbeforeEachなどを使って、
「false -> true」と「true -> false」のテストは別で実施した方が
コケた時に原因がわかりやすいです。
スナップショットテストの仕方
jestの公式ドキュメントにあるままですが、さくっと紹介します。
1. スナップショットファイルを作成、保存
下記の記述をテストコードに書き、jestを実行すると、
テストファイルの隣に __snapshots__ ディレクトリが掘られて、
そこにスナップショットファイルが作られます。
こちらのスナップショットファイルもコミットしておきましょう。
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';
it('Linkの正常系のスナップショット', () => {
const tree = renderer
.create(<Link page="http://www.instagram.com">Instagram</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
$(npm bin)/jest
2. 編集した結果、差分が出るとテストがこける
Received value does not match stored snapshot 1.
- Snapshot
+ Received
<a
className="normal"
- href="#"
+ href="#a"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
3. 意図通りの差分であれば、スナップショットを更新
スナップショットの差分もコミットすることになるので、PRで一緒にチェックができます。
$(npm bin)/jest --updateSnapshot
メソッドテストの仕方
staticなメソッド
// テスト対象
class Component extends React.Component {
static isHoge(flag1, flag2) {
return (flag1 === false || flag2 === true);
}
// 他のメソッドは省略
}
// テストパターンを定義
const dataProvider = {
'flag1=true, flag2=true なら true' : {
flag1 : true,
flag2 : true,
expected : true,
},
'flag1=true, flag2=false なら false' : {
flag1 : true,
flag2 : false,
expected : false,
},
// パターンは省略
};
//ループでテスト
Object.entries(dataProvider).forEach((testCase, desc) => {
test(desc, () => {
expect(
// スタティックなのでそのまま呼び出せる
Component.isHoge(testCase.flag1, testCase.flag2)
).toEqual(
testCase.expected
);
});
});
props, stateを参照するメソッド
// テスト対象
class Component extends React.Component {
isHoge() {
return (this.props.flag1 === false || this.props.flag2 === true);
}
// 他のメソッドは省略
}
// テストパターンを定義
const dataProvider = {
'flag1=true, flag2=true なら true' : {
props : {
flag1 : true,
flag2 : true,
},
expected : true,
},
'flag1=true, flag2=false なら false' : {
props : {
flag1 : true,
flag2 : false,
},
expected : false,
},
// パターンは省略
};
//ループでテスト
Object.entries(dataProvider).forEach(([desc, testCase]) => {
test(desc, () => {
// enzymeの.instanceを使う
const instance = shallow(<Compont {...testCase.props} />).insetance();
expect(
instance.isHoge()
).toEqual(
testCase.expected
);
});
});
stateを変更するメソッド
単体ではやり方がわからなかったです。(多分なさそう)
enzymeでレンダリングした上で、setStateなどを駆使して実施してください。
まとめ
- ReactComponentのテスト手法の分類とそれぞれの実施例を紹介
- 適宜使い分けて、どんどんコンポーネントテストを書きましょう♪