• 80
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

今回はReact.jsとテストについて書きたいと思います。

React.jsとテスト

React.jsのテストということはComponentに対してテストをすることになるので、DOMが絡んで来て辛そうだなと思うかと思いますがReact.jsはAddonとしてReact.addons.TestUtilsに便利な関数を提供してくれているのでそれを使うとテストが書きやすくなります。

DOMは必要?

React.jsのテストを書くとき、server-sideでも動くのでnodeの環境でテストを実行したくなりますが、実際にonClickイベントに反応して〜などのテストを書こうとするとやはりDOMが必要になってきます。

ちなみに、Prop渡してrenderToStaticMarkupを使ってその結果のHTMLを確認するようなテストであればnodeの環境で実行することは出来ます。

イベントのシュミレート系

「ボタン押したら〜」というテストを書こうとすると、DOMに対する参照を取得し値をセットしてイベントを発行することが必要になりますが、React.addons.TestUtils.Simulateを使うとDOMを渡して返して欲しいイベントをオブジェクトの形式で指定することが出来るので、カジュアルにユーザーアクションに対するテストを書くことが出来ます。

Simulate.{eventName}(DOMElement element, object eventData)

公式のままですがわかりやすいので...

var node = this.refs.input.getDOMNode();
React.addons.TestUtils.Simulate.click(node);
// 返したいevent objectを指定できる
React.addons.TestUtils.Simulate.change(node, {target: {value: 'Hello, world'}});
React.addons.TestUtils.Simulate.keyDown(node, {key: "Enter"});

Component作成の支援系

renderIntoDocument

テストするときは、DOMにComponentを追加しないと出来ることが限られるのでそのための関数としてrenderIntoDocumentがあります。
Componentのテスト書いていく時はとりあえずrenderIntoDocumentでDOMに追加して書いていく流れになるかと思います。

var Hello = require('./components/hello');
var component = React.addons.TestUtils.renderIntoDocument(<Hello name="foo" />);

注意点としては、これは実際にDOM Treeに追加されているわけではなくて、document.createElementで作ったdivに対してrenderしているだけなので注意が必要です。なので要素の高さなどを取得しているようなものは取得できません。
(名前が紛らわしいので最終的に変更されるかもしれないみたいです)

mockComponent

また、Jestを使ったりしていてmock化されたComponentからdummyとして<div />を返すようにするmockComponentというものもあります。

React.addons.TestUtils.mockComponent(componentClass)

この関数を使うには、componentClass.prototype.render.mockImplementationが実装されている必要があります。
なのでJest前提の関数なのかなと思います。

http://facebook.github.io/jest/docs/api.html#mockfn-mockimplementation-fn

ちょっと使いどころがわかってないですが、おそらくMock化されているけどrenderでcomponentを返して欲しい場合?に使うようです。

ちなみに実装はこんな感じで単純です。

  mockComponent: function(module, mockTagName) {
    mockTagName = mockTagName || module.mockTagName || "div";

    module.prototype.render.mockImplementation(function() {
      return React.createElement(
        mockTagName,
        null,
        this.props.children
      );
    });

    return this;
  },

Component確認系

findAllInRenderedTree(ReactComponent tree, function test)

指定されたComponent以下にあるComponentの中から指定された関数の条件を満たしたものだけを配列で返します。
これ以降で紹介するような関数が使えないような場面で使うことが出来るprimitiveな実装なので、これから紹介する関数が使えないような時に使うといいのかなと思います。

console.log(
  React.addons.TestUtils.findAllInRenderedTree(
    React.render(<div><span>foo</span><span>bar</span><p>baz</p></div>, document.body),
    function(component) { return component.tagName === "SPAN" }
  ).map(function(component){ return component.getDOMNode().textContent })
);

// ["foo", "bar"]

scryRenderedDOMComponentsWithClass(ReactComponent tree, string className)

指定されたDOMComponent以下にあるもののうち、指定されたclassNameを持っているものを配列で返します。

console.log(
  React.addons.TestUtils.scryRenderedDOMComponentsWithClass(
    React.render(
      <div>
        <span className="foo">foo1</span>
        <span className="foo">foo2</span>
        <span className="bar">barbar</span>
      </div>, 
      document.body
    ),
    'foo'
  ).map(function(component){ return component.getDOMNode().textContent })
);

// ["foo1", "foo2"]

findRenderedDOMComponentWithClass(ReactComponent tree, string className)

指定されたDOMComponent以下にあるもののうち、指定されたclassNameを持っているものがあれば1つだけ返します。
マッチするものがない場合や複数個にマッチした場合はエラーを投げます。

console.log(
  React.addons.TestUtils.findRenderedDOMComponentWithClass(
    React.render(
      <div>
        <span className="foo">foo1</span>
        <span className="foo2">foo2</span>
        <span className="bar">barbar</span>
      </div>, 
      document.body
    ),
    'foo'
  ).getDOMNode().textContent
);

// ["foo1"]
console.log(
  React.addons.TestUtils.findRenderedDOMComponentWithClass(
    React.render(
      <div>
        <span className="foo">foo1</span>
        <span className="foo">foo2</span>
        <span className="bar">barbar</span>
      </div>, 
      document.body
    ),
    'foo'
  ).getDOMNode().textContent
);

//  Uncaught Error: Did not find exactly one match for class:foo

scryRenderedDOMComponentsWithTag(ReactComponent tree, string tagName)

指定されたDOMComponent以下にあるもののうち、指定されたtagNameを持っているものを配列で返します。

console.log(
  React.addons.TestUtils.scryRenderedDOMComponentsWithTag(
    React.render(
      <div>
        <span>foo1</span>
        <span>foo2</span>
        <p>barbar</p>
      </div>, 
      document.body
    ),
    'span'
  ).map(function(component){ return component.getDOMNode().textContent })
);

// ["foo1", "foo2"]

findRenderedDOMComponentWithTag(ReactComponent tree, string tagName)

指定されたDOMComponent以下にあるもののうち、指定されたtagNameを持っているものがあれば1つだけ返します。
マッチするものがない場合や複数個にマッチした場合はエラーを投げます。

console.log(
  React.addons.TestUtils.findRenderedDOMComponentWithTag(
    React.render(
      <div>
        <span>foo1</span>
        <span>foo2</span>
        <p>barbar</p>
      </div>, 
      document.body
    ),
    'p'
  ).getDOMNode().textContent
);

// barbar

scryRenderedComponentsWithType(ReactComponent tree, function componentClass)

指定されたComponent以下にあるもののうち、指定されたcomponentClassのインスタンスであるものを配列で返します。

console.log(
  React.addons.TestUtils.scryRenderedComponentsWithType(
    React.render(
      <div>
        <Hello name="foo" key="foo" />
        <Hello name="bar" key="bar" />
        <span>xxx</span>
        <p>zzz</p>
      </div>, 
      document.body
    ),
    Hello
  ).map(function(component){ return component.getDOMNode().textContent })
);

// ["foo", "bar"]

findRenderedComponentWithType(ReactComponent tree, function componentClass)

指定されたComponent以下にあるもののうち、指定されたcomponentClassのインスタンスであるものがあれば1つだけ返します。
マッチするものがない場合や複数個にマッチした場合はエラーを投げます。

console.log(
  React.addons.TestUtils.findRenderedComponentWithType(
    React.render(
      <div>
        <Hello name="foo" key="foo" />
        <span>xxx</span>
      </div>, 
      document.body
    ),
    Hello
  ).getDOMNode().textContent
);

// foo

Assert系

React Componentの状態を確認するための関数としては下記のようなものが用意されています。

isElementOfType(ReactElement element, function componentClass)

渡したelementが指定したComponentのClassであるかを返します。

React.addons.TestUtils.isElementOfType(<Hello />, Hello);

isDOMComponent(ReactComponent instance)

渡したオブジェクトがdivやspanのようなDOMのCommponentかどうかを返します。

React.addons.TestUtils.isDOMComponent(
  React.render(<div />, document.body)
);

isCompositeComponent(ReactComponent instance)

渡したオブジェクトがReact.createClassによって定義されたComponent Classから作成されたものかどうか返します。divなど組み込みのものは含まれません。

React.addons.TestUtils.isCompositeComponent(
  React.render(<Hello />, document.body)
);

isCompositeComponentWithType(ReactComponent instance, function componentClass)

渡したオブジェクトが指定したComponent Classから作成されたものかどうか返します。

React.addons.TestUtils.isCompositeComponentWithType(
  React.render(<Hello />, document.body), Hello
);

isTextComponent(ReactComponent instance)

渡したオブジェクトがText Componentかどうか返します。

var textComponents = React.addons.TestUtils.findAllInRenderedTree(
  React.render(
    <div>{'hello'}{'react'}</div>,
    document.body
  ),
  function(component) {
    return React.addons.TestUtils.isTextComponent(component)
  }  
);
console.log(textComponents[0].props + ' ' + textComponents[1].props);
// hello react

というわけで今回はTestUtilsの使い方について説明しました。
明日はこれとJestというfacebookが作っているテスティングフレームワークを組み合わせてみたいと思います。

この投稿は 一人React.js Advent Calendar 201420日目の記事です。