jestでユニットテストを書いていたときのまとめ
スペルミスなどあるかもしれないけど雰囲気なので、気にしない
2020/04/01追記
resolves rejectsはawaitしないと、最後の呼び出し回数をカウントする際に失敗してしまう(何度呼んでも1回としてカウントされる)ので、修正
2020/04/03追記
clearAllMocksについて
そもそもユニットテストの記述に必要なもの
- 事前状態の作成(mockの作成
- 出力の評価(actualの比較、exceptionとの比較
- 事後状態の評価(mockしたオブジェクトの比較
テスト対象
意味はあまりないが、以下のようなコードがあるとして、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
}
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
:
基本完全一致させる気持ちで書いているので、複雑な文字列で書くのが 面倒なとき 本質ではないときに使う程度
書いた感じ
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をやってきた人間からすると、結構近い感覚で書けるので気持ち楽。(ユニテ自体あまり変わらないのかもしれないが