flowtype入門2

テスト編
テストもflowで書きたいという欲求

以下を加えてみた

  • mocha: テストフレームワーク
  • expect: アサーション用(mocha自体は提供しないので)
  • expect-jsx: JSXアサーション
  • react-addons-test-utils: Reactテスト用
  • jsdom: Reactテスト時 (ReactTestUtils.renderIntoDocumentを動かす際にDOM実装が必要)
  • jsdom-global: 面倒なjsdomの初期化とglobal.windowへの設定をやってくれる
  • babel-register: テストコードもes2015で書けるように

npm test でテスト実行される

package.json

{
  "name": "sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "browserify -o bundle.js -t babelify ./scripts/index.js",
    "start": "watchify -o bundle.js -t babelify ./scripts/index.js",
    "lint": "eslint scripts tests",
    "test": "mocha -r babel-register -r jsdom-global/register --recursive tests"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "engines": {
     "node": ">=6.x",
     "npm": ">=3.x"
  },
  "devDependencies": {
    "mocha": "^3.2.0",
    "expect": "^1.20.2",
    "expect-jsx": "^3.0.0",
    "react-addons-test-utils": "^15.4.2",
    "jsdom": "^9.9.1",
    "jsdom-global": "^2.1.1",
    "babel-register": "^6.18.0",
    "flow-bin": "^0.38.0",
    "babel-cli": "^6.18.0",
    "babel-plugin-transform-flow-strip-types": "^6.21.0",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "babelify": "^7.3.0",
    "browserify": "^13.3.0",
    "watchify": "^3.8.0",
    "eslint": "^3.13.1",
    "babel-eslint": "^7.1.1",
    "eslint-plugin-react": "^6.9.0",
    "eslint-plugin-flowtype": "^2.30.0",
    "eslint-plugin-flowtype-errors": "^2.0.3"
  },
  "dependencies": {
    "flux": "^3.1.2",
    "react": "^15.4.2",
    "react-dom": "^15.4.2"
  }
}

サンプル

component.state.inputValue を component.state.inputValue2にしてflowコマンド動かすと怒られるようになる。

tests/test.js

// @flow
import { describe, it } from 'mocha';
import assert from 'assert';
import Sample from '../scripts/Sample';
import React from 'react';
import ReactTestUtils, { createRenderer } from 'react-addons-test-utils';
import expect from 'expect';
import expectJSX from 'expect-jsx';

expect.extend(expectJSX);

describe('Sample', () => {
  let renderer = createRenderer();

  describe('render', () => {
    it('コンポーネントが動くこと', () => {
      renderer.render(<Sample />);
      let actualElement = renderer.getRenderOutput();
      let expectedElement = (
        <div className="sampleComponent">
          bar
        </div>
      );
      expect(actualElement).toEqualJSX(expectedElement);
    });

    it('state', () => {
      let component: Sample = ReactTestUtils.renderIntoDocument(<Sample/>);
      expect(component.state.inputValue).toBe('foo');
    });
  });
});

scripts/Sample.jsx

// @flow
import React from 'react';

type Props = {};

export default class Sample extends React.Component {
  props: Props;
  state: { inputValue: string };

  constructor(props: Props) {
    super(props);
    this.state = { inputValue: 'foo' };
  }

  render() {
    return (
      <div className="sampleComponent">
        bar
      </div>
    );
  }
}