今回は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前提の関数なのかなと思います。
ちょっと使いどころがわかってないですが、おそらく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が作っているテスティングフレームワークを組み合わせてみたいと思います。