今日は enzyme を使って TodoList コンポーネントのテストを追加する。ここ の Counter-test.js のようなものを作成していく。
まず、sinon をインストール
$ npm i sinon --save $ npm i @types/sinon --save
記事のサンプルを参考にある程度似せて書いてみるも。いくつかエラーが発生した。
1つは sinon が見つからないというもの。import sinon from 'sinon' では見つけられなかったのと、sinon の公式サイト を見ると var sinon = require('sinon'); と記述されていたので、const sinon = require("sinon") として事なきを得る。
2つ目は shallow に渡している コンポーネントの引数が一致しないというもの。結果的に、TodoList.tsx に定義している Props のメンバーをすべて引数として渡すことで解決した。(ちょっとどんくさい感じがするので後でどうにかしたい)
const spy = sinon.spy(); const state: TodoState[] = [{ id: 0, text: "aaa", completed: false }] const wrapper = shallow(<TodoList action={spy} todos={state} dispatch={{}} />);
上記を対応してテストを走らせてみるも、wrapper.find() でどうも要素を見つけられないでいるようだ。色々試行錯誤の結果 material-ui を使用している場合は、wrapper.dive().find() としなければならないようだ。この件は、こちら を参考にした。
また、検索する要素もmaterial-uiで使用しているオブジェクト名でなければならないようで、今回はタスクのクリックのテストを追加したかったのでListItem を import してそれを検索要素としている。
import ListItem from '@material-ui/core/ListItem'; // ... 中略 wrapper.dive().find(ListItem).at(0).simulate('click')
ただ、これでもまだ問題がある。クリックイベント引数 e が undefined で渡ってくるのだ。そのせいで、id = +(e.currentTarget.id) が動作せずにこける。
TypeError: Cannot read property 'currentTarget' of undefined
22 | var id:number;
23 | console.log(e)
> 24 | id = +(e.currentTarget.id)
| ^
25 | this.props.action(this.props.dispatch, completeTodo(id));
26 | }
27 |
この問題の答えは ここ にあった。simulate() 関数の第二引数にモック用イベントオブジェクトを渡せば解決する。
const mockedEvent = { currentTarget: { id: "0" }}; wrapper.dive().find(ListItem).at(0).simulate('click', mockedEvent);
次は、clickが呼ばれた後、actionが正しい引数で呼ばれたかを確認する。spy でチェックする。callCount で何回呼ばれたかをチェックし、getCall().args[] で呼ばれたときの引数をチェックする。
// componentWillMount() と on_click_li() の両方でactionが呼ばれるため2になる expect(spy.callCount).toEqual(2) expect(spy.getCall(1).args[0]).toEqual({}) expect(spy.getCall(1).args[1]).toEqual({"id": 0, "type": "COMPLETE_TODO"})
成果物は以下。
GitHub - bamchoh/react-study at 2429903815b3bb4fe2a17358da6b3cf4b8ad1925