Jest - React 第一歩


まずは簡単に props.errors で降りてきた文字列を表示するだけの componentを作る。

import * as React from "react";

interface Props {
    errors: string
}

interface State {
}

export default class Error extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);
    }

    public render() {
        return <h3>{this.props.errors}</h3>;
    }
}

上を題材にJestの初歩を勉強する。以下、Jestに動かしてもらう Unit Test を作る。Unit Test 自体も TypeScript で書いてみることにする。以下、error.test.tsx:

import * as React from "react";
import Errors from "../components/error";
import * as ReactTestRenderer from 'react-test-renderer';

test('test error component', () => {
    const errorMessage = "hello world!"
    const rendered = ReactTestRenderer.create(
        <Errors errors={errorMessage} />
    );
    const renderedJson = rendered.toJSON();
    expect(renderedJson.children[0]).toEqual(errorMessage);
});

React を import しないと、すでにTSにてエラーとなる。
[ts] 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.

上では、react-test-renderer モジュールを使用してみた。これと、Jest の expect, toEqual 等の組み合わせ。

Jest を走らせると以下の結果:

 PASS  src\__tests__\error.test.tsx
  √ test error component (16ms)

  console.log src\__tests__\error.test.tsx:10
    { type: 'h3', props: {}, children: [ 'hello world!' ] }

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.362s
Ran all test suites.
Waiting for the debugger to disconnect...
Press any key to continue . . .

となり、react-test-renderer' がDOM Tree を返してくれるのが分かる。ふむふむ、これは良い。


次に、enzyme を取り入れてみる。

import * as React from "react";
import {shallow} from "enzyme";
import Errors from "../components/error";

test('test error component', () => {
    const errorMessage = "hello world!"

    const wrapper = shallow(<Errors errors={errorMessage} />);
    const tobe = "<h3>" + errorMessage + "</h3>";
    expect(wrapper.text()).toEqual(errorMessage);
});


はい、まずはエラー発生。

Enzyme Internal Error: Enzyme expects an adapter to be configured, but found none. To
          configure an adapter, you should call `Enzyme.configure({ adapter: new Adapter() })`
          before using any of Enzyme's top level APIs, where `Adapter` is the adapter
          corresponding to the library currently being tested. For example:

          import Adapter from 'enzyme-adapter-react-15';

どこかのVersion の時点でこれが必要になったそうだ。仰せに従い、インストールする。
npm install enzyme-adapter-react-16 @types/enzyme-adapter-react-16 --save-dev

ソースを以下のように変更して走らせる。

import * as React from "react";
import {shallow} from "enzyme";
import Errors from "../components/error";

import { configure } from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

test('test error component', () => {
    const errorMessage = "hello world!"

    const wrapper = shallow(<Errors errors={errorMessage} />);
    const tobe = "<h3>" + errorMessage + "</h3>";
    expect(wrapper.text()).toEqual(errorMessage);
});

Run。また叱られた。

 console.error node_modules\fbjs\lib\warning.js:33
    Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills

requestAnimationFrame が Polyfill されていないと。これは、configure({ adapter: new Adapter() }); が実行されると発生する。この Polyfill には、https://www.npmjs.com/package/raf が良いようだ。

と、ここで落ち着いて一工夫。これらは全てのTestで必要となるであろう。毎回これらを Unit Tests の冒頭に書くのは愚。Jest には startup.cs みたいのが2種類あり、前者は Test Framework が生成される前、後者はそのあとに実行されるそうだ。

Jest の config に (pakage.json, "jest" の部)
    "setupFiles": ["<rootDir>/src/setupFile.tsx"],
    "setupTestFrameworkScriptFile": "<rootDir>/src/setupTestFrameworkScriptFile.tsx"

これらの中身は、
setupFile.tsx:
const raf = require("raf");
raf.polyfill();

setupTestFrameworkScriptFile.tsx
import { configure } from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

これにて Polyfill が先に実施されるので、後者の configure() が動作する。気を取り直して、enzyme の検証。上の処置により、Unit Test は簡素になった。

import * as React from "react";
import {shallow} from "enzyme";
import Errors from "../components/error";

test('test error component', () => {
    const errorMessage = "hello world!"

    const wrapper = shallow(<Errors errors={errorMessage} />);
    const tobe = "<h3>" + errorMessage + "</h3>";
    expect(wrapper.text()).toEqual(errorMessage);
});

結果
 PASS  src\__tests__\error.test.tsx
  √ test error component (5ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.802s, estimated 3s
Ran all test suites.
Waiting for the debugger to disconnect...
Press any key to continue . . .

ばんざーい。

さて、もう一つ、つっこんで enzyme-matchers を取り入れてみる。React の Assertion に便利そうなもので、3種の Test Framework に対応する。勿論 Jest版をインストールする。

npm add jest-enzyme --save-dev

これも毎回書くのは面倒なので、setupTestFrameworkScriptFile に追加する。

import { configure } from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
import "jest-enzyme";

これだけ。Unit Test を以下のように書き換える。

import * as React from "react";
import {shallow, mount} from "enzyme";
import Errors from "../components/error";

test('test error component', () => {
    const errorMessage = "hello world!"

    const wrapper = shallow(<Errors errors={errorMessage} />);
    const tobe = "<h3>" + errorMessage + "</h3>";
    expect(wrapper.text()).toEqual(errorMessage);
    expect(wrapper).toHaveProp("errors");
});

Test自体はきちんと走るが、enzyme-matchers が提供する toHaveProp() で assert error となる。shallow() を mount() で置き換えると、assert error は起きない。ようわからんが、shallow() では props がセットされ無いようだ。難しい議論はこちら: https://github.com/airbnb/enzyme/issues/445

---
mock や stub 等々とりあえず置いておけば、これにてひとまず Unit Test をがりがり書ける環境が整ったと思う。何を走らせてどの様に assert するかは、おいおい個別のプロジェクトで勉強することにしよう。

Just が提供する assertions はこちら:

Enzyme の提供する render wrapperはこちら:

enzyme-matchers (jest-enzyme) の提供する assertions はこちら:

ばんざーい


コメント

このブログの人気の投稿

HiddenFor 要注意

SPA を IIS から流す際の ASP 側のルーティング

Jest の テスト・スクリプトをデバッグする術