React.jsとテスト

今回は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)
```

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

```js
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に追加して書いていく流れになるかと思います。


```js
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`というものもあります。

```js
React.addons.TestUtils.mockComponent(componentClass)
```

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

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

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


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

```js
  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な実装なので、これから紹介する関数が使えないような時に使うといいのかなと思います。

```js
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`を持っているものを配列で返します。

```js
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`を持っているものがあれば１つだけ返します。
マッチするものがない場合や複数個にマッチした場合はエラーを投げます。

```js
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"]
```

```js
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`を持っているものを配列で返します。

```js
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`を持っているものがあれば１つだけ返します。
マッチするものがない場合や複数個にマッチした場合はエラーを投げます。

```js
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`のインスタンスであるものを配列で返します。

```js
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`のインスタンスであるものがあれば１つだけ返します。
マッチするものがない場合や複数個にマッチした場合はエラーを投げます。

```js
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であるかを返します。

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

### isDOMComponent(ReactComponent instance)

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

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

### isCompositeComponent(ReactComponent instance)

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

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


### isCompositeComponentWithType(ReactComponent instance, function componentClass)

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

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


### isTextComponent(ReactComponent instance)

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

```js
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が作っているテスティングフレームワークを組み合わせてみたいと思います。