React ComponentのテストをquerySelectorでやる

React ComponentのテストをquerySelectorでやる

使いなれた'ul.element-list li.element'などで要素にアクセスし、propsに応じて意図した描画がされているか確認します。

React Componentのプロパティではなく、HTMLElementによる検査をしたいというのが目的です。innerHTMLとか。

テスト環境

Mocha+以下のライブラリを使います。

import assert from 'power-assert';
import * as ReactDOM from 'react-dom';
import * as TestUtils from 'react-addons-test-utils'

import YourComponent from "../src/components/your-component";

テスト環境準備の包括的な話は以下の記事が参考になるかもしれません。 JavaScript - テストがないJS環境にモダンなテスト環境を導入していく - Qiita

カバレッジは、特にあとからテストを書く場合に、テスト抜けが容易に観測できておすすめです。

setupメソッドの準備

React ComponentのインスタンスとそのDOMNodeにアクセスできるようにします。

各テストファイルに、ターゲットとなるReact Component用のsetupメソッドを準備します。

propsを引数にすることにより、各状況のテストが容易に行なえます。

function setup(props) {
  let rendered = TestUtils.renderIntoDocument(<YourComponent {...props} />);
  let dom = ()=> ReactDOM.findDOMNode(rendered);
  let find = (selector)=> dom().querySelector(selector);
  let findAll = (selector)=> Array.prototype.slice.call(dom().querySelectorAll(selector));

  return {rendered, dom, find, findAll }
}

find, findAll

目的の要素セレクター関数です。

rendered

React Componentのインスタンスに直接手を入れられます。

dom

生Node。

Redux Routerなどトップレベルのコンポーネントをすげかえる動作をする場合、ReactDOM.findDOMNodeで取得したものとは別のDOMNodeになるため、変化が観察できません。

なので関数として都度取得するようにしています。

テストする

おなじみのセレクターをつかって、テストしていきます。

it('index', ()=>{
  let elements = [
    {id:1, title:'title1'},
    {id:2, title:'title2'}
  ];
  let {findAll} = setup({elements});
  let children = findAll('ul.element-list li.element a');

  assert.equal(children.length, 2);  
  assert.equal(children[0].innerHTML, 'title1');  
});

find、findAllで選択した要素に対し、TestUtils.Simulateを用いてイベントを発行することもできます。

リンクのクリックなどのテストの場合、ハンドラーが呼ばれるのがテスト終了の必須条件ですから、doneメソッドを用いてテスト終了を明示的に行うようにします。

it('link', (done)=>{
  let elements = [
    {id:1, title:'title1'},
    {id:2, title:'title2'}
  ];
  let link = (id)=>{
    assert.equal(id, 1);
    done();
  }
  let {findAll} = setup({elements, link});
  let anchor = findAll('ul.element-list li.element a')[0];  

  TestUtils.Simulate.click(anchor);
});