87
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React.jsAdvent Calendar 2015

Day 1

ReactのテストをEnzymeで書いてみよう

Last updated at Posted at 2015-12-10

GithubのExploreを覗いていたら面白そうなテストツールがありました。
https://github.com/airbnb/enzyme

Reactに力を入れているというAirbnbが作ったテスティングツールなのですが、ドキュメントがサラッとしか書いてなかったのでお遊びがてら使えるようになるまで触った軌跡を書いてきます。

「んなことより早くブツを見せんかい!!」という猛者JSerな方は以下のリポジトリから何となく意図を汲みとって何となくマサカリを投げてください。

はじめに

まずは必要なものをnpmで入れてきましょう。

$ npm install -g mocha
$ npm install --save-dev chai
$ npm install --save-dev babel-register
$ npm install --save-dev enzyme

インストールしたものはそれぞれ

  • mocha(テスティングフレームワーク)
  • chai(アサーションライブラリ)
  • babel-register(importしたライブラリを実行前にトランスパイルしてくれる)
  • enzyme(今回の主役)

ちなみに今回はmocha・chaiでやってますが別にこれじゃないと出来無い訳ではないのでお好みで。

次にnpm-scriptでbuildとtestを担うスクリプトを作成しましょう。

package.json
  "scripts": {
    "build": "browserify --debug index.jsx --outfile bundle.js -t [ babelify --presets [ es2015 react ] ]",
    "test": "mocha --compilers js:babel-register"
  }

buildの方は、まずbabelifyでindex.jsxをトランスパイルしてその結果をbundle.jsとしてbrowserifyで出力しています。
testはmochaを使い、jsはbabel-registerを用いて実行するようにしています。

ファイルを用意しよう

次に諸々の必要なファイルを用意しましょう。
今回は以下の様なディレクトリ構成でやっています。

% tree -L 1
.
├── Foo.jsx (Fooコンポーネントが詰まったファイル)
├── My.jsx (Myコンポーネントが詰まったファイル)
├── README.md
├── bundle.js (ビルド後のファイル)
├── index.html (表示用HTML)
├── index.jsx (コンポーネントを取りまとめるためのファイル)
├── node_modules
├── package.json
└── test (テストが詰まったディレクトリ)

それでは、Foo.jsx・My.jsx・index.jsxをそれぞれ作成しましょう。

Foo.jsx
import React from 'react'
import ReactDOM from 'react-dom'

let Foo = React.createClass({
  render: () => {
    return (
      <p>こんにちは!</p>
    )
  }
})

export default Foo;
My.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import Foo from './Foo.jsx'

let MyComponent = React.createClass({
  render: () => {
    return (
      <div>
        <h1>Hello, World!</h1>
        <Foo />
        <Foo />
        <Foo />
      </div>
    )
  }
})

export default MyComponent;
index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import MyComponent from './My.jsx'

ReactDOM.render(
  <MyComponent />,
  document.getElementById('contents')
)

FooコンポーネントとMyコンポーネントは、最後に export default xxx; を使ってimportされた時にコンポーネントが呼び出されるようにしています。
そしてindex.jsxはMyコンポーネントを、MyコンポーネントではFooコンポーネントを順に呼び出しています。

それではテストファイルを作りましょう。

$ mkdir test
test/test.js
import React from 'react'
import ReactDOM from 'react-dom'
import { shallow, mount, render } from 'enzyme'
import { assert, expect } from 'chai'
import MyComponent from '../My.jsx'
import Foo from '../Foo.jsx'

describe('<MyComponent />', () => {

  it('should render three <Foo /> components', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find(Foo)).to.have.length(3);
  });

  it('should render an `.icon-star`', () => {
    const wrapper = shallow(<MyComponent />);
    expect(wrapper.find('.icon-star')).to.have.length(1);
  });

  it('should render children when passed in', () => {
    const wrapper = shallow(
      <MyComponent>
        <div className="unique" />
      </MyComponent>
    );
    expect(wrapper.contains(<div className="unique" />)).to.be.true;
  });

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = shallow(
      <Foo onButtonClick={onButtonClick} />
    );
    wrapper.find('button').simulate('click');
    expect(onButtonClick.calledOnce).to.be.true;
  });

});

ほぼ例の通りにやりました。
今回はコンポーネントの階層が浅いのでshallow renderingでテストを書きましたが、
コンポーネント階層を深くまでテストをしたい場合は、JSDOM Full Renderingを用いてください。

また、テストの記述方法ですが__describe__ではどのコンポーネントに対してテストを行うかを設定していて、__it__ではそのテストの内容を書いていきます。
RSpecのような書き方が出来るのは魅力的ですね。

ちなみに今回は1つ目のテストだけパスするように作ってあります。

テストの実行

それでは実際にテストを実行してみましょう。

$ npm run test
> enzyme-sandbox@1.0.0 test /Users/hoge/enzyme-sandbox
> mocha --compilers js:babel-register

  <MyComponent />
    ✓ should render three <Foo /> components
    1) should render an `.icon-star`
    2) should render children when passed in
    3) simulates click events


  1 passing (124ms)
  3 failing

  1) <MyComponent /> should render an `.icon-star`:
     AssertionError: expected { Object (root, unrendered, ...) } to have a length of 1 but got 0
      at Context.<anonymous> (test.js:17:48)

  2) <MyComponent /> should render children when passed in:
     AssertionError: expected false to be true
      at Context.<anonymous> (test.js:26:63)

  3) <MyComponent /> simulates click events:
     ReferenceError: sinon is not defined
      at Context.<anonymous> (test.js:30:27)

こんな感じの結果になりました。
見た通り、1つ目のテストだけパスしてますね。

とりあえず、ザッとこんな感じで動かすことが出来ました。
ReactのテストをRSpecのように整然と書くことが出来るのはなかなか便利ですね。
Airbnbが開発のため、これからの積極的なアップデートも楽しみです。

今回はこのような形でテストを行いましたが、「もっとこんな形でしたほうがいいよ」という方がおりましたらコメント欄等でのご指摘お願い致します。

87
76
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
87
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?