6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JestとTesting-LibraryとMockについてまとめる

Posted at

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();
  });
});

非同期

asyncawaitを使用し、非同期の要素に対して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関数は、呼び出しの回数や引数の内容を記録したり、実際の実装とは関係なく指定した戻り値を返すようにしたり、実際の実装を上書きしたりできる。
コールバック関数のテストなどに良い。

jest.fn()
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()で元の実装に戻すことができる。

jest.spyOn()
  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()describetestの中に記述しても機能しないので注意。

Test.jsx
export const foo = () => {
  return 'foo'
}

export const bar = () => {
  return 'baz'
}
Test.test.jsx
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()describetestの中に記述しても機能しないので注意

参考

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?