LoginSignup
7
5

More than 3 years have passed since last update.

jestでのユニットテストを書いた際の個人的まとめ

Last updated at Posted at 2020-03-14

jestでユニットテストを書いていたときのまとめ
スペルミスなどあるかもしれないけど雰囲気なので、気にしない

2020/04/01追記
resolves rejectsはawaitしないと、最後の呼び出し回数をカウントする際に失敗してしまう(何度呼んでも1回としてカウントされる)ので、修正

2020/04/03追記
clearAllMocksについて

そもそもユニットテストの記述に必要なもの

  1. 事前状態の作成(mockの作成
  2. 出力の評価(actualの比較、exceptionとの比較
  3. 事後状態の評価(mockしたオブジェクトの比較

テスト対象

意味はあまりないが、以下のようなコードがあるとして、service.tsをテストする

service.ts
import * as logic form './logic'

export testTarget = (a: number, b: number): Promise<number> => {
  const result = logic.calc(a, b)
  const saveResult = await logic.save({
    text: `save: ${result}`
  })
  if (!saveResult) {
    throw new Error('cannot add result')
  }
  return result
}
logic.ts
export calc = (a: number, b: number): number => {
  // 適当に計算をするものとする
  return 1
}

export save = (object: { text: string }): Promise<boolean> => {
  // 適当になにかに保存するものとする
  return Promise.resolve(true);
}

テストを書く

1. 事前状態の作成(mockの作成

私が基本的に使うのは以下3つ
* mockReturnValueOnce: 指定のオブジェクトを一度返す
* mockResolvedValueOnce: 指定のオブジェクトをPromise.resolveで包んで返す
* mockRejectedValueOnce: 指定のオブジェクトをPromise.rejectで包んで返す

mock対象の関数がPromiseを返すかで使い分ける。また、mock対象が複数回呼ばれるのであればチェインして書く。

2. 出力の評価(actualの比較、exceptionとの比較

私が基本的に使うのは以下3つ
* toBe: 単純比較(number、string、boolean
* toStrictEqual: Array、Objectの比較
* toThrowError: エラーの比較

3. 事後状態の評価(mockしたオブジェクトの比較

私が基本的に使うのは以下2つ
* toBeCalledTimes: 呼び出した回数の確認、回数が不定の場合は toHaveBeenCalled でカバー
* toHaveBeenCalledWith: 呼び出した際の引数の確認、複数回の場合は「順番に」複数回書く
* toHaveBeenNthCalledWith: 呼び出した際の引数の確認、n回目のものを検証したい場合に使う。nは1始まり

そして上記toHaveBeenCalledWithに与える引数として以下2つ
* expect.stringMatching:
* expect.not.stringMatching:

基本完全一致させる気持ちで書いているので、複雑な文字列で書くのが 面倒なとき 本質ではないときに使う程度

書いた感じ

service.spec.ts
import * as targets from './service'

jest.mock('./logic')
import * as logic from './logic'
const logicMock = logic as jest.Mocked<typeof logic>

describe('testTarget', () => {
  beforeEach(() => {
    // mockのクリア
    jest.clearAllMocks()
  })
  test('saveが成功する', async () => {
    // 1. 事前状態の作成
    logicMock.calc.mockReturnValueOnce(3)
    logicMock.save.mockResolvedValueOnce(true)

    // 2. 出力の評価
    const actual = targets.testTarget(1, 2)
    await expect(actual).resolves.toBe(2)

    // 3. 事後状態の評価
    expect(logicMock.calc).toBeCalledTimes(1)
    expect(logicMock.calc).toHaveBeenCalledWith(1, 2)
    expect(logicMock.save).toBeCalledTimes(1)
    expect(logicMock.save).toHaveBeenCalledWith({
      text: 'save: 3'
    })
  })
  test('saveが失敗する', async () => {
    // 1. 事前状態の作成
    logicMock.calc.mockReturnValueOnce(3)
    logicMock.save.mockRejectedValueOnce(new Error('なんかやばいエラー'))

    // 2. 出力の評価
    const actual = targets.testTarget(1, 2)
    await expect(actual).rejects.toThrowError(new Error('なんかやばいエラー'))

    // 3. 事後状態の評価
    expect(logicMock.calc).toBeCalledTimes(1)
    expect(logicMock.calc).toHaveBeenCalledWith(1, 2)
    expect(logicMock.save).toBeCalledTimes(1)
    expect(logicMock.save).toHaveBeenCalledWith({
      text: 'save: 3'
    })
  })
})

気をつけること

例えば、消し忘れていたなどでmockの挙動を定義していて、実際に呼ばれていない場合 clearAllMocks がmockを削除できず、そのまま次のテストでその挙動が実行されてしまう. jest: version 25.2.4

感想

他のユニテのフレームワークをきちんと理解しているわけではないが、かなりわかりやすく書ける。
Javaをやってきた人間からすると、結構近い感覚で書けるので気持ち楽。(ユニテ自体あまり変わらないのかもしれないが

7
5
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
7
5