Jestまとめ - y-okamon/jest-sample GitHub Wiki
matcher(マッチャー)
単純な値
test('two plus two is four', () => {
expect(2 + 2).toBe(4); // OK
});
test('string check', () => {
expect('hoge').toBe('hoge'); // OK
});
オブジェクトや配列
toEqual
は、オブジェクトまたは配列の全フィールドを再帰的にチェックする。
オブジェクトの比較
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2}); // OK
});
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
// two, one の順序を入れ替えた
expect(data).toEqual({two: 2, one: 1}); // OK
});
配列の比較
test('array check', () => {
const data = [1, 2, 3];
expect(data).toEqual([1, 2, 3]); // OK
});
test('array check', () => {
const data = [1, 2, 3];
expect(data).toEqual([1, 2, 4]); // NG
});
反対のテスト
test('not zero', () => {
expect(1 + 2).not.toBe(0); // OK
});
真偽値
toBeNull
はnull
のみ一致するtoBeUndefined
はundefined
のみ一致するtoBeDefined
はtoBeUndefined
と反対になるtoBeTruthy
はifステートメント
で真であるとみなすものに一致するtoBeFalsy
はifステートメント
で偽であるとみなすものに一致する
test('null', () => {
const n = null;
// all OK
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('undefined', () => {
const n = undefined;
// all OK
expect(n).not.toBeNull();
expect(n).not.toBeDefined();
expect(n).toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('zero', () => {
const z = 0;
// all OK
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
数値
test('two plus two', () => {
const value = 2 + 2;
// all OK
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// 数値の場合は、toBe と toEqual は同等である
expect(value).toBe(4);
expect(value).toEqual(4);
});
浮動小数点の値が同一であるか確認する場合はtoBeCloseTo
を使う。
→丸め込み誤差が原因で期待通り動作しないため
test('adding floating point numbers', () => {
const value = 0.1 + 0.2;
expect(value).toBe(0.3); // NG
expect(value).toBeCloseTo(0.3); // OK
});
toBe
でもOKになる場合はあるがtoBeCloseTo
と使い分けるのは面倒なので常に後者を使う方が良さそう。
test('adding floating point numbers', () => {
const value = 0.5 + 0.25;
expect(value).toBe(0.75); // OK
expect(value).toBeCloseTo(0.75); // OK
});
文字列
正規表現を使用できる。
test('there is no I in team', () => {
expect('team').not.toMatch(/I/); // OK
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/); // OK
});
配列と反復可能なオブジェクト
toContain
を使用すると、配列や反復可能なオブジェクトに特定のアイテムが含まれていることを確認できる。
const shoppingList = [
'apple',
'orange',
'banana',
];
test('toContain check', () => {
expect(shoppingList).toContain('banana'); // OK
expect(new Set(shoppingList)).toContain('apple'); // OK
});
例外
toThrow
を使用すると、例外をスローすることを確認できる。
function doAction() {
throw new Error('test error throw function.');
}
test('throw Error check', () => {
expect(doAction).toThrow(); // OK
expect(doAction).toThrow(Error); // OK
// エラーメッセージまたは正規表現指定も可能
expect(doAction).toThrow('test error'); // OK
expect(doAction).toThrow('error test'); // NG
expect(doAction).toThrow('test error throw function.'); // OK
expect(doAction).toThrow(/throw/); // OK
});
その他
公式リファレンスを参照のこと(matcherだけでもかなりの数がある)
https://jestjs.io/docs/ja/expect
Testing Asynchronous Code
テスト用の関数作成
// 2000ms後に引数callbackを呼ぶだけの関数
const testCallback = (callback) => {
setTimeout(() => callback('success'), 2000);
};
// promiseサンプル
// 引数がtrueならばresolve(), falseならばreject()する
const testPromise = isSuccess => {
return new Promise((resolve, reject) => {
isSuccess ? resolve('success') : reject('error');
});
};
module.exports = {
testCallback: testCallback,
testPromise: testPromise
}
コールバック
fetchData
が完了した時点でテスト終了となりcallback
が呼ばれないため、意図した動作にならない。
test('Async callback check', () => {
function callback(data) {
expect(data).toBe('success');
}
fetchData(callback);
});
done
という1つの引数を利用する。
Jestはdone
コールバックが呼ばれるまで待ち、呼ばれたらテスト終了となる。
これで意図したように動作する。
const fetchData = require('../async');
test('Async callback check', done => {
function callback(data) {
try {
expect(data).toBe('success'); // OK
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
Promises
テストからpromiseを返すと、Jestはそのpromiseがresolve()されるまで待機する。
promiseがreject()された場合は、テストは自動的に失敗する。
test('Async promise(resolve) check', () => {
return myAsync.testPromise().then(data => {
expect(data).toBe('success'); // OK
});
});
promiseがreject()されることを期待するケースでは.catch
メソッドを使用する。
想定した数のアサーションが呼ばれたことを確認するためexpect.assertions
を必ず追加すること。
test('Async promise(reject) check', () => {
expect.assertions(1);
return myAsync.testPromise(false).catch(err => {
expect(err).toMatch('error'); // OK
});
});
.resolves
/ rejects
expect文で.resolves
マッチャを使用することもできる。
test('Async promise(resolve) check', () => {
return expect(myAsync.testPromise(true)).resolves.toBe('success'); // OK
});
同様に.resolves
マッチャを使用することもできる。
promiseが成功した場合は、テストは自動的に失敗します。
test('Async promise(reject) check', () => {
return expect(myAsync.testPromise(false)).rejects.toMatch('error'); // OK
});
Async / Await
async
とawait
をテストで使用できる。
test
に渡す前の関数にasync
キーワードを記述すれば良い。
// resolve.
test('Async promise(async/await) check', async () => {
const data = await myAsync.testPromise(true);
expect(data).toBe('success'); // OK
});
// reject.
test('Async promise(async/await) check', async () => {
expect.assertions(1);
try {
await myAsync.testPromise(false);
} catch (e) {
expect(e).toMatch('error'); // OK
}
});
async
とawait
を.resolves
または.reject
と組み合わせることもできる。
test('Async promise(async/await) check', async () => {
await expect(myAsync.testPromise(true)).resolves.toBe('success'); // OK
});
test('Async promise(async/await) check', async () => {
await expect(myAsync.testPromise(false)).rejects.toMatch('error'); // OK
});
Setup and Teardown
テストごとにセットアップ作業を繰り返し実行する
多くのテストで繰り返し行う必要がある処理がある場合は、beforeEach
とafterEach
を使用します。
// テスト開始前に実行したい処理
beforeEach(() => {
console.log('call beforeEach');
});
// テスト終了後に実行したい処理
afterEach(() => {
console.log('call afterEach');
});
test('dummy test 1', () => {
expect(true).toBeTruthy();
});
test('dummy test 2', () => {
expect(true).toBeTruthy();
});
このテストを実行すると、以下の順に処理が実行される。
- beforeEach
- dummy test 1
- afterEach
- beforeEach
- dummy test 2
- afterEach
ワンタイムセットアップ
セットアップがファイルの先頭で1回だけ実行される。
beforeAll(() => {
console.log('call beforeAll');
})
afterAll(() => {
console.log('call afterAll');
})
test('dummy test 1', () => {
expect(true).toBeTruthy();
});
test('dummy test 2', () => {
expect(true).toBeTruthy();
});
このテストを実行すると、以下の順に処理が実行される。
- beforeAll
- dummy test 1
- dummy test 2
- afterAll
スコープ
describe
ブロックを使って複数のテストをグループ化することができる。
before
とafter
ブロックはdescribe
ブロックの中のテストにだけ適用される。
// ファイル内のすべてのテストに作用する
beforeEach(() => {
console.log('beforeEach');
});
test('dummy test 1', () => {
console.log('test 1');
expect(true).toBeTruthy();
});
test('dummy test 2', () => {
console.log('test 2');
expect(true).toBeTruthy();
});
describe('dummy describe', () => {
// describeブロック内のテストにのみ作業する
beforeEach(() => {
console.log('beforeEach in describe');
});
test('dummy test 1 in describe', () => {
console.log('test 1 in desc');
expect(true).toBeTruthy();
});
test('dummy test 2 in describe', () => {
console.log('test 2 in desc');
expect(true).toBeTruthy();
});
});
このテストを実行すると、以下の順に処理が実行される。
"beforeEach"
は常に呼び出され、"beforeEach in describe"
はdescribe
ブロック内でのみ呼び出されていることがわかる。
- beforeEach
- test 1
- beforeEach
- test 2
- beforeEach
- beforeEach in describe
- test 1 in desc
- beforeEach
- beforeEach in describe
- test 2 in desc
describeおよびtestブロックの実行順序
公式にconsole.log()
を埋め込んだ具体的コードあり
Mock Functions
モック関数を利用する
確認用のモジュールを作成する。
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
module.exports = {
forEach: forEach
}
モック関数を利用する。全てのモック関数は.mock
プロパティがあり、モック関数呼び出し時のデータと、関数の返り値が記録されている。
test('モック関数を利用する', () => {
const myMock = require('../mock');
const mockCallback = jest.fn(x => 42 + x);
myMock.forEach([0, 1], mockCallback);
// mock関数は2回呼ばれる
expect(mockCallback.mock.calls.length).toBe(2);
// 1回目の呼び出し時の引数は0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// 2回目の呼び出し時の引数は1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// 1回目の呼び出し時の戻り値は42
expect(mockCallback.mock.results[0].value).toBe(42);
// 2回インスタンス化された
expect(mockCallback.mock.instances.length).toBe(2);
});
モックの戻り値
モック関数は、テスト中のコードにテスト用の値を注入するのにも利用できる。
test('モックの戻り値', () => {
const myMock = jest.fn();
console.log(myMock()); // > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
});
test('モックの戻り値', () => {
const filterTestFn = jest.fn();
// 1回目のコール時はtrueを返し、2回目のコール時はfalseを返す
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result); // [11] (trueのみが抽出されている)
console.log(filterTestFn.mock.calls); // [ [ 11 ], [ 12 ] ]
});
モックの実装
jest.fn
またはモック関数のmockImplementationOnce
メソッドを利用することで実現できます。
test('モックの実装', () => {
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
});
mockImplementation
メソッドは他のモジュールによって作成されたモック関数のデフォルトの実装を定義したいときに使用すると便利です。
// sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
// test
test('モックの実装_mockImplementationOnceメソッド', () => {
jest.mock('../sum')
const sum = require('../sum');
sum.mockImplementation((a, b) => a * b);
sum(2, 3); // 6
});
関数への複数回への呼び出しで異なる結果を得るようモック関数を再作成する必要がある場合
test('モックの実装_複数回の呼び出しで異なる結果を得る', () => {
const myMockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));
myMockFn((err, val) => console.log(val)); // true
myMockFn((err, val) => console.log(val)); // false
});
mockImplementationOnce
で定義された実装を全て使い切った場合はjest.fn
のデフォルトの実装を実行する。
test('モックの実装_mockImplementationOnceメソッドを使い切った場合', () => {
const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => '1st call')
.mockImplementationOnce(() => '2nd call');
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
});
カスタムマッチャ
マッチャのカスタムができる。
// ======== カスタムマッチャ ========
test('カスタムマッチャ', () => {
const myMock = jest
.fn(scalar => 42 + scalar)
.mockName('add42');
myMock(1);
myMock(2);
myMock(3);
// 1度は呼ばれた
expect(myMock).toHaveBeenCalled();
expect(myMock.mock.calls.length).toBeGreaterThan(0);
// 1度は指定の引数で呼ばれた
expect(myMock).toHaveBeenCalledWith(1);
expect(myMock.mock.calls).toContainEqual([1]);
expect(myMock.mock.calls).toContainEqual([2]);
expect(myMock.mock.calls).toContainEqual([3]);
// expect(myMock.mock.calls).toContainEqual([4]); // NG
// 最後に指定の引数で呼ばれた
expect(myMock).toHaveBeenLastCalledWith(3);
expect(myMock.mock.calls[myMock.mock.calls.length - 1]).toEqual([
3
]);
});
関数のモックについて
spyOn
を使用する。
使い方:jset.spyOn(モジュール変数, '関数名')
// spyOn確認用
const item = {
title() {
return 'item title';
}
};
module.exports = {
item: item
}
describe('mock spyOn test', () => {
const { item } = require('../mock');
test('item title test', () => {
// mockReturnValueOnceによって自由にモック化できる。
// jest.spyOnだけでは(実際の)モック化されていない関数が実行される。
// jset.spyOn(モジュール変数, '関数名')
const spy = jest
.spyOn(item, 'title')
.mockReturnValueOnce('item sub title');
// mockReturnValueOnceによって'item title'ではなくなる。
expect(item.title()).toBe('item sub title'); // OK
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});
});