Jest・Testing-Library・Mockを使用してテストケースを記述した際に学んだことや注意点などをまとめる。
JestとTesting-Libraryを用いたテスト
テストケースの定義
describe
でテストをグループ化し、test
で実際のテストケースを定義する。
expect
にテストしたいものを記述し、想定される結果をマッチャーを使用して記載する。
下記の例では、toBe(true)
がマッチャーであり、想定される結果がtrue
であることを記述している。
他にもtoContain
(配列に特定の要素が含まれている)など様々なマッチャーがある。
describe('〇〇コンポーネント', () => {
test('テストケース1', () => {
expect(true).toBe(true);
});
});
コンポーネントのレンダリング
render
で指定したコンポーネントをレンダリングできる。
レンダリング後に要素の取得などを行うことで、レンダリング結果に対するテストを記述できる。
screen.debug
関数でレンダリングされたHTMLを確認できる。
describe('App', () => {
test('renders App component', () => {
render(<App />);
expect(screen.getByText('Search:')).toBeInTheDocument();
});
});
getByText
などのgetBy
から始まるものは指定された要素を取得し、なければエラーを返す。
getAllByText
などAll
が付加されたものもあり、その場合は指定された要素をすべて取得する。
queryBy
から始まるものは指定された要素が存在しない場合にエラーを返さないため、指定された要素が存在しないことを確認する場合に用いる。
findBy
から始まるものは、非同期処理のものに用いる。対象が取得できる状態になるまで待機して取得してくれる。
イベントの発火
userEvent
を使用する。
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';
describe('App', () => {
test('renders App component', async () => {
render(<App />);
// wait for the user to resolve
await screen.findByText(/Signed in as/);
expect(screen.queryByText(/Searches for JavaScript/)).toBeNull();
await userEvent.type(screen.getByRole('textbox'), 'JavaScript');
expect(
screen.getByText(/Searches for JavaScript/)
).toBeInTheDocument();
});
});
非同期
async
とawait
を使用し、非同期の要素に対してfindBy
を使って記述する。
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
describe('App', () => {
test('renders App component', async () => {
render(<App />);
expect(screen.queryByText(/Signed in as/)).toBeNull();
screen.debug();
expect(await screen.findByText(/Signed in as/)).toBeInTheDocument();
screen.debug();
});
});
Mock
特定の関数を模倣することができる。
Mockを使用すると、下記のようなことが可能となり、テストが実装しやすくなる。
- 関数の呼び出し回数や引数を記録・参照
- 関数の戻り値の書き換え
- 関数の実装の書き換え
Mockの方法は大きく3パターンある。
jest.fn()
mock関数を作成する。
mock関数は、呼び出しの回数や引数の内容を記録したり、実際の実装とは関係なく指定した戻り値を返すようにしたり、実際の実装を上書きしたりできる。
コールバック関数のテストなどに良い。
test('jest.fn()', () => {
const mockfn = jest.fn()
//返り値を指定。
//mockReturnValueOnceでは呼び出した順に返す返り値を指定できる。
//mockReturnValueOnceを使い切った場合は、mockReturnValueの値が使用される。
mockfn
.mockReturnValue(0)
.mockReturnValueOnce(1)
.mockReturnValueOnce(2)
const firstReturnValue = mockfn(1);
const secondReturnValue = mockfn(2);
const thirdReturnValue = mockfn(3);
//呼び出しは3回。mockプロパティを参照することで引数や呼び出し回数が取得できる。
expect(mockfn.mock.calls).toHaveLength(3);
//2回目の呼び出しの第1引数は2
expect(mockfn.mock.calls[1][0]).toBe(2);
//返り値は順に1,2,0
expect(firstReturnValue).toBe(1)
expect(secondReturnValue).toBe(2)
expect(thirdReturnValue).toBe(0)
//mock関数の実装
mockfn.mockImplementation(() => { return 3 });
//実装により返り値として3が返る
expect(mockfn()).toBe(3)
})
jest.spyOn()
オブジェクトのメソッドをmockできる。
元のメソッドの実装を変えずに監視したり、元のメソッドの実装を書き換えたりできる。
実装を書き換えた場合、テストを跨いで適用されてしまうので、mockRestore()
で元の実装に戻すことができる。
test('jest.spyOn()', () => {
//オブジェクトのメソッドを定義
const testObj = {
fn: (i) => { return i }
}
//オブジェクトのメソッドをスパイする
const spy = jest.spyOn(testObj, 'fn');
//元のメソッドの実装は変わらない
expect(testObj.fn(3)).toBe(3)
//mock関数同様、呼び出し回数などを取得できる
expect(spy.mock.calls).toHaveLength(1);
//元のメソッドの実装を上書き。変更はテストをまたぐ。
spy.mockImplementation((i) => { return i + 100 });
expect(testObj.fn(3)).toBe(103)
//元のメソッドの実装へ戻す
spy.mockRestore();
expect(testObj.fn(3)).toBe(3)
})
jest.mock()
モジュールをmockできる。
モジュールでexport
されている関数それぞれに対し、jest.fn()
と同様の操作が可能。
jest.mock()
はdescribe
やtest
の中に記述しても機能しないので注意。
export const foo = () => {
return 'foo'
}
export const bar = () => {
return 'baz'
}
jest.mock('../Test.jsx')
test('jest.mock()', () => {
//fooの返り値を上書き
foo.mockReturnValue('foofoofoo');
expect(foo()).toBe('foofoofoo');
//mockプロパティにもアクセス可能
expect(foo.mock.calls).toHaveLength(1)
//barの実装を上書き
bar.mockImplementation(() => { return 'baz' })
expect(bar()).toBe('baz')
//barのメソッド確認用
console.log({ bar })
})
console.log()
でbar
の内容を確認した結果も記載する。
{
bar: [Function: bar] {
_isMockFunction: true,
getMockImplementation: [Function (anonymous)],
mock: [Getter/Setter],
mockClear: [Function (anonymous)],
mockReset: [Function (anonymous)],
mockRestore: [Function (anonymous)],
mockReturnValueOnce: [Function (anonymous)],
mockResolvedValueOnce: [Function (anonymous)],
mockRejectedValueOnce: [Function (anonymous)],
mockReturnValue: [Function (anonymous)],
mockResolvedValue: [Function (anonymous)],
mockRejectedValue: [Function (anonymous)],
mockImplementationOnce: [Function (anonymous)],
withImplementation: [Function: bound withImplementation],
mockImplementation: [Function (anonymous)],
mockReturnThis: [Function (anonymous)],
mockName: [Function (anonymous)],
getMockName: [Function (anonymous)]
}
}
その他注意点
-
getByText
は要素が複数あった場合、最初の要素を取得する -
getByRole
は要素が複数あった場合、エラーになる-
{name: text}
で絞り込みできる
-
-
input type=number
に対してuserEvent.type()
が上手く適用できなかった-
fireEvent.change()
を使うと問題ない
-
findBy
を使う際にawait
を忘れると正しく動作しないので注意jest.mock()
はdescribe
やtest
の中に記述しても機能しないので注意
参考