検証環境
Jest
React Testing Library
Create React App(上記が含まれるので)
背景
サードパーティ製のComponentを避けてテストを行うために、ComponentをMockする必要がありました。
特に、Amplify-uiなど事前設定が必要なComponentに対しては有効な手段となります。
テスト環境を作成
# 作業フォルダを作成
mkdir work && cd work
# Package ManagerでCRAを利用してReact Applicationを生成する
pnpm create react-app jest-app --template typescript
# フォルダに移動
cd jest-app
# componentsとtestフォルダを作成
mkdir src/components src/tests
# Editorを開く
code .
Componentを作成
以下をサードパーティ製のComponentやテスト範囲外の"複雑なComponent"と仮定します。
export default function ComplexDefaultComponent() {
return <div>Default exported complex component</div>;
}
export function ComplexNamedFunction() {
return <div>Named exported complex component</div>;
}
以下がテスト対象のComponentです。上記の"複雑なComponent"を用いて構成されています。
import ComplexDefaultComponent, { ComplexNamedFunction } from './complex-component';
export default function ParentComponent() {
return (
<div>
<ComplexDefaultComponent />
<ComplexNamedFunction />
<div>Parent component</div>
</div>
);
}
Mock無しのテストをまず生成します。
import { render, screen } from '@testing-library/react';
import ParentComponent from '../components/parent-component';
test('ParentComponent', () => {
render(<ParentComponent />);
screen.debug();
// Mock無し場合は、当然複雑なComponentがそのまま表示されます。
expect(screen.getByText('Default exported complex component')).toBeInTheDocument();
expect(screen.getByText('Named exported complex component')).toBeInTheDocument();
});
<body>
<div>
<div>
<div>
Default exported complex component
</div>
<div>
Named exported complex component
</div>
<div>
Parent component
</div>
</div>
</div>
</body>
実際にMockしてみる
jest.mock()
を使用します。
※import * as xxx from 'xxx'
とすれば、jest.sypOn()でも可能ですが、手間が増えるので採用しません。
Default exportとNamed exportのMockのされ方に注意してください。
以下のようにテストファイルを書き換えます。Mockされた内容でテストは成功します。
import { render, screen } from '@testing-library/react';
import ParentComponent from '../components/parent-component';
// Jest内でHoistingされるので、jest.mockはModuleスコープで宣言する必要があります
jest.mock('../components/complex-component', () => {
return {
// Default exportされるObjectを明示的にexportするためのdフラグ
__esModule: true,
// Default export
default: () => <div>Mock default export</div>,
// Named export
ComplexNamedFunction: () => <div>Mock named export</div>,
};
});
test('ParentComponent', () => {
render(<ParentComponent />);
screen.debug();
// 本来のComponentは表示されません
expect(screen.queryByText('Default exported complex component')).not.toBeInTheDocument();
expect(screen.queryByText('Named exported complex component')).not.toBeInTheDocument();
// MockされたComponentが表示されます
expect(screen.getByText('Mock default export')).toBeInTheDocument();
expect(screen.getByText('Mock named export')).toBeInTheDocument();
});
もちろん中身もMockされていますね。
<body>
<div>
<div>
<div>
Mock default export
</div>
<div>
Mock named export
</div>
<div>
Parent component
</div>
</div>
</div>
</body>
最後に
上記のようにMockすることで、テスト範囲を限定し、効果的なテストを行うことが可能です。
ちなみに記事によっては、jest.mock('xxx', () => () => {return <div>ooo</div>})
のように、default
やNamed export
を明示せずともMockできるとありましたが、私の環境ではエラーが発生し、うまくいきませんでした。
テストコードは、コードレビューやモジュール分離などのヒントも与えてくれます。ドキュメント上のテストの仕組みだけでなく、実際に手を動かすことで見えてくるものがあるので、これからも自分に負けずにテストを書いていきたいです。