読者です 読者をやめる 読者になる 読者になる

JestでReactのテストをする(4) propsの値をテストする

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
};

nameagehasIdという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によって0falseに型変換されるからである。
このことからも、処理自体は通常通り行われることが分かる。

そのため、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の記述は、上記のように先頭に書いても、あるいは中間や末尾に書いても、問題なく機能した。
先頭に書くのが、見やすい気はする。