メインコンテンツへスキップ
Version: 27.0

Reactアプリをテスト

Facebook ではJestを使用して、Reactアプリケーションをテストします。

セットアップ#

Create React Appを使用したセットアップ#

Reactに馴染みがないなら、Create React Appの利用をお勧めします。 すぐに使えて Jestも同梱されています! スナップショットをレンダリングするには、 react-test-renderer を追加するだけです。

実行

yarn add --dev react-test-renderer

Create React Appを使わないセットアップ#

既存のアプリケーションがある場合は、いくつかのパッケージをインストールしてうまく機能するようにする必要があります。 babel-jestパッケージと reactのbabel presetをテスト環境内のコードを変換するのに利用しています。 using babelも参照して下さい。

実行

yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

package.jsonは以下のようなものになっているはずです( <current-version>はパッケージの実際の最新版のバージョンの数字になります)。 scriptsとJestの設定のエントリを追加して下さい:

"dependencies": {
"react": "<current-version>",
"react-dom": "<current-version>"
},
"devDependencies": {
"@babel/preset-env": "<current-version>",
"@babel/preset-react": "<current-version>",
"babel-jest": "<current-version>",
"jest": "<current-version>",
"react-test-renderer": "<current-version>"
},
"scripts": {
"test": "jest"
}
// babel.config.js
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};

それでは次へ進みましょう!

スナップショットテスト#

ハイパーリンクをレンダリングするLinkコンポーネントの snapshot test を作成しましょう:

// Link.react.js
import React, {useState} from 'react';
const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};
const Link = ({page, children}) => {
const [status, setStatus] = useState(STATUS.NORMAL);
const onMouseEnter = () => {
setStatus(STATUS.HOVERED);
};
const onMouseLeave = () => {
setStatus(STATUS.NORMAL);
};
return (
<a
className={status}
href={page || '#'}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</a>
);
};
export default Link;

Note: Examples are using Function components, but Class components can be tested in the same way. See React: Function and Class Components. Reminders that with Class components, we expect Jest to be used to test props and not methods directly.

コンポーネントとのやり取りとレンダリングされた出力をキャプチャしてスナップショットファイルを作成するために、ReactのテストレンダラーとJestのスナップショット機能を利用しましょう:

// Link.react.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';
test('Link changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseLeave();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

yarn test または jestを実行すると、このようなファイルが出力されます:

// __tests__/__snapshots__/Link.react.test.js.snap
exports[`Link changes the class when hovered 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
Facebook
</a>
`;
exports[`Link changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
Facebook
</a>
`;
exports[`Link changes the class when hovered 3`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
Facebook
</a>
`;

次回のテストでは、レンダリングされた出力は前に作成されたスナップショットと比較されます。 スナップショットは、コードの変更に沿ってコミットされるべきです。 スナップショットテストが失敗した場合、それが意図的な変更かどうかを点検する必要があります。 変更が予想されたものであればjest -uコマンドでJestを実行して既存のスナップショットを上書きします。

The code for this example is available at examples/snapshot.

モック、Enzyme および React 16 を使用したスナップショットテスト#

Enzyme と React 16 以降を使用している場合、スナップショットテストには注意点があります。 以下のスタイルを使用しているモジュールをモックアウトする場合:

jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');

コンソールに次のような警告が表示されます。

Warning: <SomeComponent /> is using uppercase HTML. Always use lowercase HTML tags in React.
# または
Warning: The tag <SomeComponent> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

React 16 がこれらの警告を引き起こしてしまう理由は、要素の型チェックの方法のためであり、モック化したモジュールはこのチェックに引っかかってしまいます。 これに対処するための選択肢は以下のとおりです。

  1. テキストとしてレンダリングする。 この方法を選んだ場合、スナップショット内のモックコンポーネントに渡された props を確認することができませんが、シンプルで分かりやすい方法です。
    js
    jest.mock('./SomeComponent', () =&#062; () =&#062; 'SomeComponent');
  2. カスタム要素としてレンダリングする。 DOM "カスタム要素" は一切チェックされないため、警告も発生しません。 名前にダッシュと小文字が使われます。
    tsx
    jest.mock('./Widget', () =&#062; () =&#062; <mock-widget />);
  3. react-test-renderer を使用する。 test renderer は、要素の型を気にしないので、SomeComponent といった要素を許容してくれます。 test renderer を使うと、スナップショットをチェックしたり、Enzyme とは独立してコンポーネントのふるまいを確認することができます。
  4. 警告をすべて無効にします(jestの設定ファイルで行う必要があります):
    jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
    警告をすべて無効にします(jestの設定ファイルで行う必要があります): js jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction')); これは、有用な警告が失われる可能性があるため、通常とるべき選択肢ではありません。 しかし、例えばreact-nativeのコンポーネントをテストする場合には、react-nativeタグをDOMにレンダリングする際に、多くの無関係な警告が発生します。 別の選択肢としては、console.warning を利用して、特定の警告を抑制することです。

DOM のテスト#

レンダリングされたコンポーネントをアサートし操作したいのなら、react-testing-libraryEnzyme もしくは Reactの TestUtilsが利用できます。 以下の2つの例では、react-testing-library と Enzyme を使用します。

react-testing-library#

react-testing-libraryを利用するには、 yarn add --dev @testing-library/react を実行する必要があります。

2つのラベルを入れ替えるチェックボックスを実装しましょう。

// CheckboxWithLabel.js
import React, {useState} from 'react';
const CheckboxWithLabel = ({labelOn, labelOff}) => {
const [isChecked, setIsChecked] = useState(false);
const onChange = () => {
setIsChecked(!isChecked);
};
return (
<label>
<input type="checkbox" checked={isChecked} onChange={onChange} />
{isChecked ? labelOn : labelOff}
</label>
);
};
export default CheckboxWithLabel;
// __tests__/CheckboxWithLabel-test.js
import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';
// Note: running cleanup afterEach is done automatically for you in @testing-library/react@9.0.0 or higher
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);
it('CheckboxWithLabel changes the text after click', () => {
const {queryByLabelText, getByLabelText} = render(
<CheckboxWithLabel labelOn="On" labelOff="Off" />,
);
expect(queryByLabelText(/off/i)).toBeTruthy();
fireEvent.click(getByLabelText(/off/i));
expect(queryByLabelText(/on/i)).toBeTruthy();
});

The code for this example is available at examples/react-testing-library.

Enzyme#

You have to run yarn add --dev enzyme to use Enzyme. If you are using a React version below 15.5.0, you will also need to install react-addons-test-utils.

react-testing-libraryの代わりにEnzymeを使用して上記のテストを書き直しましょう。 この例ではEnzymeの shallow renderer を使用します。

// __tests__/CheckboxWithLabel-test.js
import React from 'react';
import {shallow} from 'enzyme';
import CheckboxWithLabel from '../CheckboxWithLabel';
test('CheckboxWithLabel changes the text after click', () => {
// Render a checkbox with label in the document
const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);
expect(checkbox.text()).toEqual('Off');
checkbox.find('input').simulate('change');
expect(checkbox.text()).toEqual('On');
});

The code for this example is available at examples/enzyme.

独自のコード変換処理#

より高度な機能が必要な場合は、独自の変換処理を構築することもできます。 babel-jestを使用する代わりに、 @babel/core を使用する例を示します:

// custom-transformer.js
'use strict';
const {transform} = require('@babel/core');
const jestPreset = require('babel-preset-jest');
module.exports = {
process(src, filename) {
const result = transform(src, {
filename,
presets: [jestPreset],
});
return result || src;
},
};

この例を動作させるには @babel/corebabel-preset-jest パッケージをインストールすることを忘れないで下さい。

これをJestと動作させるにはJestに次の設定を追加する必要があります:"transform": {"\\.js$": "path/to/custom-transformer.js"}

Babelのサポート機能を使ってコードの変換処理を構築したい場合は、babel-jestを変換処理の作成に利用し、その中に独自の構成オプションを渡すことができます。

const babelJest = require('babel-jest');
module.exports = babelJest.createTransformer({
presets: ['my-custom-preset'],
});

詳細は ドキュメント を参照してください。