メインコンテンツへスキップ
バージョン: 29.7

Reactアプリのテスト

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

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

セットアップ

Create React Appを使ったセットアップ

Reactを初めて使う場合は、Create React Appの使用をお勧めします。すぐに使える状態でJestが同梱されています!スナップショットのレンダリングにはreact-test-rendererの追加のみが必要です。

実行:

npm install --save-dev react-test-renderer

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

既存のアプリケーションがある場合、すべてを適切に動作させるためにいくつかのパッケージをインストールする必要があります。テスト環境内でコードを変換するためにbabel-jestパッケージとreactのBabelプリセットを使用します。詳細はUsing Babelも参照してください。

実行:

npm install --save-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', {runtime: 'automatic'}],
],
};

これで準備完了です!

スナップショットテスト

ハイパーリンクをレンダリングするLinkコンポーネントのスナップショットテストを作成しましょう:

Link.js
import {useState} from 'react';

const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};

export default function 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>
);
}
メモ

例では関数コンポーネントを使用していますが、クラスコンポーネントも同様の方法でテストできます。React: Function and Class Componentsを参照してください。注意点: クラスコンポーネントでは、Jestを使ってメソッドを直接テストするのではなくpropsをテストすることを想定しています。

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

Link.test.js
import renderer from 'react-test-renderer';
import Link from '../Link';

it('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
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();

// manually trigger the callback
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

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

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

exports[`changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`changes the class when hovered 3`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

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

この例のコードは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.

# Or:
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は表示されませんがシンプルです:

    jest.mock('./SomeComponent', () => () => 'SomeComponent');
  2. カスタム要素としてレンダリング: DOMの「カスタム要素」はチェックされず警告が発生しません。小文字で名前にはハイフンが含まれます:

    jest.mock('./Widget', () => () => <mock-widget />);
  3. react-test-rendererを使用: テストレンダラーは要素タイプを気にせず、例えばSomeComponentを受け入れます。テストレンダラーでスナップショットを確認し、Enzymeで別途コンポーネントの動作を確認できます。

  4. 警告を完全に無効化(Jestのセットアップファイルで実施):

    jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));

    通常は推奨されません(有用な警告が失われる可能性があるため)。ただし、React Nativeコンポーネントをテストする場合など、React NativeタグをDOMにレンダリングする際には多くの警告が無関係です。別の方法として、console.warnを上書きして特定の警告を抑制することもできます。

DOMテスト

レンダリングされたコンポーネントの操作やアサーションには、@testing-library/reactEnzyme、ReactのTestUtilsが使用できます。以下の例では@testing-library/reactを使用します。

@testing-library/react

npm install --save-dev @testing-library/react

2つのラベルを切り替えるチェックボックスを実装してみましょう:

CheckboxWithLabel.js
import {useState} from 'react';

export default function 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>
);
}
__tests__/CheckboxWithLabel-test.js
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();
});

この例のコードは examples/react-testing-library で利用可能です。

カスタムトランスフォーマー

より高度な機能が必要な場合、独自のトランスフォーマーを構築することもできます。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'],
});

詳細については、専用ドキュメントを参照してください。