Reactのコンポーネントには、属性として値を渡すことができ、渡された値にはthis.props.xxxでアクセスできる。
propsの他に、自身で管理する値であるstateというものもあるが、Reactの思想的にstateはあまり使わないほうがいいのだろう。
コンポーネントはただ値を受け取り、その値に従って出力(レンダリング)を行うものであるべき。
propsには何でも渡すことが出来るが、propTypesという機能を使って、渡された値をチェックすることが出来る。
詳しくは下記を参照。
React.jsのProp - Qiita
このpropTypesをテストの対象とするのが、今回の目的。
例として、以下のようなPersonコンポーネントを用意した。
import React from 'react'; export default class Person extends React.Component{ constructor(props){ super(props); }; render(){ const id = this.props.hasId ? '有' : '無' ; return( <div> <h1>{this.props.name}</h1> <p>年齢: {this.props.age}</p> <p>会員証: {id}</p> </div> ); }; }; Person.propTypes = { name: React.PropTypes.string.isRequired, age: React.PropTypes.number.isRequired, hasId: React.PropTypes.bool.isRequired };
name、age、hasIdという3つのpropsを受け取るが、全て必須であり、それぞれ文字列、数値、真偽値、でないといけない。
例えば<Person name="Tom" hasId={true} />とレンダリングすると、以下の警告が出る。
Warning: Failed prop type: Required prop `age` was not specified in `Person`.
in Person
ただ、あくまでもconsole.warn()でメッセージが出るだけであり、エラーにはならない。
レンダリングも行われる。年齢が入るべき部分は、空白となる。
次に、<Person name="Tom" age={20} hasId={0} />とレンダリングしてみる。真偽値であるべきhasIdが、数値になっている。
Warning: Failed prop type: Invalid prop `hasId` of type `number` supplied to `Person`, expected `boolean`.
in Person
正しく、警告が出ている。ただやはりレンダリングはされるし、しかも、会員証の部分に「無」と表示されている。
これは、JavaScriptによって0がfalseに型変換されるからである。
このことからも、処理自体は通常通り行われることが分かる。
そのため、propsの値がおかしかったとしても、テストでは見逃されてしまう。
例えば、以下のようなテストを書いたとする。
import React from 'react'; import {shallow} from 'enzyme'; import Person from '../types.js'; describe('propTypesのテスト', ()=>{ test('正しいケース', ()=>{ const subject = shallow(<Person name="Tom" age={20} hasId={true} />); }); test('ageがない', ()=>{ const subject = shallow(<Person name="Tom" hasId={true} />); }); test('hasIdの型が違う', ()=>{ const subject = shallow(<Person name="Tom" age={20} hasId={0} />); }); });
コンポーネントに渡される値が正しくないが、テストはパスする。
console.warn()でメッセージが表示されるので見落とすことはないかもしれないが、テスト自体を通らないようにしたほうが、テストとしては望ましいと思う。
そこで、以下の記述を追加することで、テストに引っかかるようになる。
console.error = error => { throw new Error(error); };
以下のテストを実行すると、テストは通らない。
import React from 'react'; import {shallow} from 'enzyme'; import Person from '../types.js'; describe('propTypesのテスト', ()=>{ console.error = error => { throw new Error(error); }; test('正しいケース', ()=>{ const subject = shallow(<Person name="Tom" age={20} hasId={true} />); }); test('ageがない', ()=>{ const subject = shallow(<Person name="Tom" hasId={true} />); }); test('hasIdの型が違う', ()=>{ const subject = shallow(<Person name="Tom" age={20} hasId={0} />); }); });
FAIL __tests__/types.test.js
● propTypesのテスト › ageがない
Warning: Failed prop type: Required prop `age` was not specified in `Person`.
in Person
at CustomConsole.console.error (__tests__/types.test.js:7:9)
at Object.<anonymous> (__tests__/types.test.js:13:107)
at process._tickCallback (internal/process/next_tick.js:103:7)
● propTypesのテスト › hasIdの型が違う
Warning: Failed prop type: Invalid prop `hasId` of type `number` supplied to `Person`, expected `boolean`.
in Person
at CustomConsole.console.error (__tests__/types.test.js:7:9)
at Object.<anonymous> (__tests__/types.test.js:16:107)
at process._tickCallback (internal/process/next_tick.js:103:7)
propTypesのテスト
✓ 正しいケース (10ms)
✕ ageがない (3ms)
✕ hasIdの型が違う (1ms)
console.errorの記述は、上記のように先頭に書いても、あるいは中間や末尾に書いても、問題なく機能した。
先頭に書くのが、見やすい気はする。