Jestとは
JestはJavaScriptの単体テストのフレームワークです。
https://jestjs.io/ja/
テストランナーだけでなく、モック機能やカバレッジの取得を使用することができます。npmのトレンドとしては2019年から伸びてmochaを超えるものとなっています。
https://www.npmtrends.com/jest-vs-jasmine-vs-mocha-vs-qunit
この記事は公式のサンプルコードを弄ってその挙動を確認するものとなっています。
また、実験環境は以下の通りです。
OS:MacOS Catalina 10.15.6
node.js: v14.10.1
簡単なはじめ方
jestを使用するためにnode.jsのプロジェクトを以下のように作成します。
# package.jsonを作成する
npm init -y
# package.jsonにjestを追加してnode_modulesにインストール
npm install --save-dev jest
jestではデフォルトでは__tests__フォルダ中のjavascritpをテストコードとして実行するので、__tests__フォルダにテストコードを追加します。
test('test 1.', ()=>{
expect(1+2).toBe(3);
});
テストを動かすには以下のコマンドを実行します。
# テストを開始する
npx jest
設定ファイル
jestの設定は以下のいずれかの方法で指定することができます。
- package.jsonに記載する
- jest.config.jsに記載する
- 実行時に--configオプションで指定したファイルに指定する
jest.config.jsの作成
下記のコマンドで対話式でjest.config.jsを作成します。
npx jest --init
カバレッジの収集方法
下記のように--coverageオプションを指定して実行することでカバレッジの収集を行います。
npx jest --coverage
このコマンドを実行するとテストが実行されて、coverageフォルダに結果が格納されます。
coverage/lcov-report/index.htmlにはhtml形式でレポートが表示されています。
Jenkins用のテストレポートを作成
Jenkins用のファイルを出力するには下記のプラグインが必要になります。
https://www.npmjs.com/package/jest-jenkins-reporter
以下のコマンドでインストールしてください。
npm install --save-dev jest-jenkins-reporter
レポートの出力方法の指定はjest.config.jsとpackage.jsonを使用します。
// 略
testResultsProcessor: "jest-jenkins-reporter",
// 略
"jestSonar": {
"reportPath": "reports",
"reportFile": "test-reporter.xml",
"indent": 4
}
この設定を行った後にjestを実行するとreportsフォルダにtest-reporter.xmlに保存されます。
Jenkinsはこれを利用することで単体テストの結果を取得します。
VSCodeを使用したテストコードのデバッグ方法
VSCodeでテストコードをデバッグするにはワークスペースに以下のファイルを作成します。
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach",
"port": 9229
}
]
}
次にターミナル上で以下のコマンドを実行します。
node --inspect-brk node_modules/.bin/jest --runInBand __tests__/matcher.test.js
この後に、VSCodeのAttachボタンを押下することでデバッグ実行が可能になります。
VSCodeのテストランナー
Jest Test Explore
(2021/11追記)
vscode-jestを導入した方がいいです。Jest Test Explorerと同様な操作感で使用でき、vueでも安定して動作します。
https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest
Jest Test Explorerを使用することでVSCode上でテストが実行できる。
ここのテストを選択してテストを簡単に実行することが可能。
https://marketplace.visualstudio.com/items?itemName=kavod-io.vscode-jest-test-adapter
このプラグインはnode_modules/.bin/jestを使用して実行している。
このvueで使用する場合は以下のようにパスを変更する必要がある
{
"jestTestExplorer.pathToJest": "/full/workspace/path/node_modules/.bin/vue-cli-service test:unit"
}
非同期の試験
do引数の利用
テストメソッドの引数にdoneを与えて、テストメソッド中にそのdoneを実行することで任意のタイミングでテストの終了を通知することができます。
function async_func() {
return new Promise(resolve =>{
console.log('start async_func');
setTimeout(function() {
console.log('end async_func');
resolve('done!');
}, 100);
});
}
test('async callback test', done=> {
async_func().then(msg => {
expect(msg).toBe('done!');
done();
});
});
doneを使用してテストメソッド中に呼ばれない場合はタイムアウトのエラーとなります。
test('async callback not called', done=> {
async_func();
});
Promisesの試験
テスト対象のメソッドがPromisesを返す場合、テストメソッドでそのPromisesを返すことで、それが解決するまでテストメソッドの完了を待つことができます。
マッチャー関数のresolves/rejectsを使用してPromissesの結果を判定することも可能です。
function async_func() {
return new Promise(resolve =>{
console.log('start async_func');
setTimeout(function() {
console.log('end async_func');
resolve('done!');
}, 100);
});
}
function async_func_err() {
return new Promise((resolve, reject) =>{
console.log('start async_func_err');
setTimeout(function() {
console.log('end async_func_err');
reject('error!');
}, 100);
});
}
test('async promiss resolve test 1', ()=> {
return async_func().then(msg => {
console.log('resolve test');
expect(msg).toBe('done!');
});
});
test('async promiss resolve test 2', ()=> {
return expect(async_func()).resolves.toBe('done!');
});
test('async promiss reject test', ()=> {
return async_func_err().catch(msg => {
console.log('reject test');
expect(msg).toBe('error!');
});
});
test('async promiss reject test 2', ()=> {
return expect(async_func_err()).rejects.toBe('error!');
});
async/awaitの使用
async と awaitをテスト中に使用することも可能です。
test('await ', async ()=> {
const ret = await async_func();
console.log(ret);
expect(ret).toBe('done!');
});
test('await 2', async () => {
await expect(async_func()).resolves.toBe('done!');
});
setup,teardown使用したテスト毎の処理
各テストの開始前と開始後に特定の処理を実行することができます。
-
beforeAll
- ファイル内のいずれかのファイルを実行する場合に実行される。
-
afterAll
- ファイル内の全てのテストが完了した際に実行される。
-
beforeEach
- ファイルまたはテストスイート内の各テストが実行される前に、関数を実行する。
-
afterEach
- ファイルまたはテストスイート内の各テストが完了した際に、関数を実行する。
これらの関数はpromiseを返すことができ、jestはそのpromiseが解決するまでは処理が進みません。このデフォルトのタイムアウトは5000msとなっており、以下のように第二引数でタイムアウトの時間を調整することが可能です。
// 500msタイムアウトするのでエラーとなるサンプル
afterAll(()=> {
console.log('afterall');
return new Promise(r=> setTimeout(r, 1000));
}, 500);
beforeEach/afterEachは階層化されたdescribeごとに設定することができます。下記のコードはその実行順を検証したものとなります。
beforeAll(() => {
console.log('beforeAll');
});
afterAll(() => {
console.log('afterAll');
});
beforeEach(() => {
console.log('before Each アウトスコープ');
});
afterEach(() => {
console.log('after Each アウトスコープ');
});
test('test1', ()=> {
console.log('tes1 を実行する');
});
test('test2', ()=> {
console.log('tes2 を実行する');
});
describe('テストスィート', ()=> {
beforeEach(() => {
console.log('before Each スコープ内');
});
afterEach(() => {
console.log('after Each スコープ内');
});
test('test3', ()=> {
console.log('test3 を実行する');
});
test('test4', ()=> {
console.log('test4 を実行する');
});
});
test('test5', ()=>{
console.log('test5を実行する');
});
このコードの実行結果は以下のようになります。
describe中で指定したbeforeEach,afterEachは、その構造を考慮した順番で実行されていることが確認できます。
● Console
console.log
beforeAll
at Object.<anonymous> (__tests__/setup_teardown.test.js:2:11)
console.log
before Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:10:11)
console.log
tes1 を実行する
at Object.<anonymous> (__tests__/setup_teardown.test.js:18:11)
console.log
after Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:14:11)
console.log
before Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:10:11)
console.log
tes2 を実行する
at Object.<anonymous> (__tests__/setup_teardown.test.js:22:11)
console.log
after Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:14:11)
console.log
before Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:10:11)
console.log
before Each スコープ内
at Object.<anonymous> (__tests__/setup_teardown.test.js:27:13)
console.log
test3 を実行する
at Object.<anonymous> (__tests__/setup_teardown.test.js:33:13)
console.log
after Each スコープ内
at Object.<anonymous> (__tests__/setup_teardown.test.js:30:13)
console.log
after Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:14:11)
console.log
before Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:10:11)
console.log
before Each スコープ内
at Object.<anonymous> (__tests__/setup_teardown.test.js:27:13)
console.log
test4 を実行する
at Object.<anonymous> (__tests__/setup_teardown.test.js:36:13)
console.log
after Each スコープ内
at Object.<anonymous> (__tests__/setup_teardown.test.js:30:13)
console.log
after Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:14:11)
console.log
before Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:10:11)
console.log
test5を実行する
at Object.<anonymous> (__tests__/setup_teardown.test.js:41:11)
console.log
after Each アウトスコープ
at Object.<anonymous> (__tests__/setup_teardown.test.js:14:11)
console.log
afterAll
at Object.<anonymous> (__tests__/setup_teardown.test.js:6:11)
特定のテストを実行するまたは実行しない。
test,it,describeにはskip、onlyを使用してテストを実行するかどうかの制御を行うことができます。
skipは動かなくなったテストを暫定的にスキップする場合や、負荷が高くて通常は実行したくないテストを弾く場合に使用します。
まだ未実装のことを現したい場合はtodoを使用しましょう。todoではエラーを出力します。
onlyはデバッグ中に特定のテストのみを動作させる場合に使用します
なお、MsTestにあるpriorityは存在しないので、優先度を指定してのテストは行えないようです。
describe('desc1', () => {
test.skip('test1', () => {
});
test('test2', () => {
// これだけ実行される
});
});
describe.skip('desc2', ()=> {
test('test3', () => {
});
test('test4', () => {
});
});
describe.only('desc1', () => {
test('test1', () => {
// 実行される
});
test('test2', () => {
// 実行される
});
});
describe('desc2', ()=> {
test('test3', () => {
});
test('test4', () => {
});
});
describe.only('desc3', ()=> {
test.only('test5', ()=> {
// 実行される
});
test('test6', ()=> {
});
});
モック
jestではモックが使用できます。これを利用してテスト対象に依存する関数をテストに都合のいい振る舞いにすることができます。
特定の関数をモックにする例
spyOnを用いることで特定の関数をモックにすることができます。
テスト対象の関数
function sum(a, b) {
return a + b;
}
function minus(a, b) {
return a - b;
}
module.exports = {
sum : sum,
minus : minus
}
テストコード
const calc = require('../calc');
test('test sum.', ()=> {
expect(calc.sum(1, 2)).toBe(3);
});
test('特定の関数についてmockを使用して特定の値を返却する', ()=> {
// モックを設定する
console.log('spyOnの前', calc.sum, calc.minus);
jest.spyOn(calc, 'sum').mockReturnValue(5);
console.log('spyOnの後', calc.sum, calc.minus);
// モックを呼び出す
expect(calc.sum(1,2)).toBe(5);
// モックを元の関数に戻す
calc.sum.mockRestore();
console.log('mockRestoreの後', calc.sum, calc.minus);
expect(calc.sum(1,2)).not.toBe(5);
});
test('特定の関数についてmockを使用して特定の関数を実行する', ()=> {
jest.spyOn(calc, 'sum').mockImplementation((a,b) => {
console.log('mock function');
return 100;
});
expect(calc.sum(1,2)).toBe(100);
jest.restoreAllMocks();
expect(calc.sum(1,2)).toBe(3);
});
モジュール内の関数を全てモックにする
jest.mockを使用することで、jest.mockを実行したファイル内では、そのモジュールをモックとして使用します。
この例ではテストコード中でfsモジュールの関数をモックとして使用します。
テスト対象のモジュール
const fs = require('fs');
module.exports = function(path) {
var text = fs.readFileSync(path, 'utf8');
var lines = text.toString().split('\n');
return lines;
}
テストコード
const read_file = require('../read_file');
const fs = require('fs')
jest.mock('fs');
test('test 1.', ()=>{
const contents = 'test\ntest2\ntest3'
fs.readFileSync.mockReturnValue(contents);
const results = read_file('test.txt')
console.log(results);
expect(results).toEqual(['test','test2','test3']);
});
test('test 2.', ()=>{
const contents = ''
fs.readFileSync.mockReturnValue(contents);
const results = read_file('test.txt')
console.log(results);
expect(results).toEqual([""]);
});
マニュアルモックの使用例
マニュアルモックはモックデータを返すスタブを別ファイルに作成することができます。
__mocks__サブディレクトリにモックモジュールを作成します。
モックモジュール
'use strict';
const fs = jest.createMockFromModule('fs');
fs.readFileSync = function(path, enc) {
return '1234\nabcde\n56789\n';
}
module.exports = fs;
テストコード
テストコードではfs.readFileSyncを使用するとモックモジュールで指定した関数が実行されます。
const read_file = require('../read_file');
const fs = require('fs')
jest.mock('fs');
test('manual mock.', ()=>{
const results = read_file('test.txt')
expect(results).toEqual(['1234','abcde','56789', '']);
});
タイマーと時刻の偽装
useFakeTimers を使用することでsetTimeout, setInterval, clearTimeout, clearInterval, nextTick, setImmediate、clearImmediateなどのタイマー関係の関数やシステム時刻の偽装を行えます。
システム時刻の偽装
setSystemTimeで偽の時間を指定することができます。ただし、これはuseFakeTimersでmodernを指定した時のみ有効です。
時刻を偽装しているときに本当の時刻を取得したい場合はgetRealSystemTimeを使用します。
時刻の偽装を止める場合はuseRealTimersを使用します。
test('時計のモックの確認', ()=> {
const dumytime = new Date(2020, 0, 1, 23, 55,40);
expect(new Date()).not.toEqual(dumytime);
// Fakeの時間を使用する
jest.useFakeTimers('modern');
jest.setSystemTime(new Date(2020, 0, 1, 23,55,40));
expect(new Date()).toStrictEqual(dumytime);
console.log(new Date(jest.getRealSystemTime()));
expect(new Date(jest.getRealSystemTime())).not.toStrictEqual(dumytime);
// 通常の時間を使う
jest.useRealTimers();
expect(new Date()).not.toEqual(dumytime);
});
タイマーの挙動の偽装
以下ではsetTimeoutの偽装を行いモックとして利用する例を示します。
https://jestjs.io/docs/ja/timer-mocks
テスト対象の関数
'use strict';
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
テストコード
test('waits 1 second before ending the game', () => {
console.log(setTimeout);
// setTimeout, setInterval, clearTimeout, clearInterval, nextTick, setImmediate
// そして clearImmediateを偽装する
jest.useFakeTimers();
// setTimeoutがmockConstructorになる
console.log(setTimeout);
const timerGame = require('../timerGame');
timerGame();
// setTimeoutがどのように実行されたかを確認する
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
// タイマーシステムが保留しているあらゆるタイマーを削除します。
// これを実行しないと他のテストでjest.runAllTimersを実行した際にsetTimeoutのコールバックが動作する
jest.clearAllTimers()
// 本物のTimer関数を使用するように戻す。
jest.useRealTimers()
});
useFakeTimers()を使用すると以降、setTimeoutがモックの関数になります。
偽装したタイマーを進める
偽装したタイマーはadvanceTimersByTimeで指定のmsを経過したことにすることができます。
test('タイマーを経過させる', () => {
jest.useFakeTimers();
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// At this point in time, the callback should not have been called yet
expect(callback).not.toBeCalled();
// 半分だけ進める
jest.advanceTimersByTime(500);
expect(callback).not.toBeCalled();
// 残り半分を進める
jest.advanceTimersByTime(500);
// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
jest.clearAllTimers()
jest.useRealTimers()
});
偽装したタイマーのコールバックを実行する
偽装されたsetTImerはjest.runAllTimersまたはjest.runOnlyPendingTimersで設定したコールバックを実行することができます。
runAllTimersでは全てのタイマーを実行し、runOnlyPendingTimersは保留中のタイマーのみ実行します。
再帰的なタイマーの場合、runAllTimersを使用すると無限ループになるので、runOnlyPendingTimersを使用するようにします。
テスト対象のコード
再帰的なタイマーを使用しているとします。
// infiniteTimerGame.js
'use strict';
function infiniteTimerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up! 10 seconds before the next game starts...");
callback && callback();
// Schedule the next game in 10 seconds
setTimeout(() => {
infiniteTimerGame(callback);
}, 10000);
}, 1000);
}
module.exports = infiniteTimerGame;
テストコード
test('再帰的なタイマーを実行した場合', () => {
jest.useFakeTimers();
const infiniteTimerGame = require('../infiniteTimerGame');
const callback = jest.fn();
infiniteTimerGame(callback);
// At this point in time, the callback should not have been called yet
expect(callback).not.toBeCalled();
// これだと無限ループになる
// jest.runAllTimers();
jest.runOnlyPendingTimers();
// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledTimes(2);
jest.clearAllTimers()
jest.useRealTimers()
});
期待値の確認
expectを使用して確認します。
test('test', ()=> {
// test1()の結果が1であるか確認する
expect(test1()).toBe(1);
// test1()の結果が2でないことを確認する。
expect(test1()).not.toBe(2);
}
notを使用することで条件の反転が可能です。
-
- プリミティブ値を比較したり、オブジェクトインスタンスの参照IDを確認したりします。
- 浮動小数点のチェックには使用しないでください。
-
- 浮動小数点の近似的な値を比較します。
- デフォルトでは小数点の第二位までチェックします。
test('浮動小数点 - 桁数の確認', ()=> {
// デフォルトでは有効桁二桁まで確認するーこれは合格する
expect(0.1+0.201).toBeCloseTo(0.3);
// 第二引数に有効桁を指定する。ーこれはNGとなる
expect(0.1+0.201).toBeCloseTo(0.3,3);
})
-
- 真偽値のチェックを行う。
-
- undefinedか否かの確認
-
- undefinedではないことの確認
-
- オブジェクトが再起的に一致するか確認する。
function test5() {
return {
name : 'Abc',
age : 15,
group: {
name : "Def"
}
}
}
test('equal', ()=> {
expect(test5()).toEqual({
name : 'Abc',
age : 15,
group: {
name : "Def"
}
});
});
-
toStrictEqual
- toEqualに加えて型が一致するかも確認している
class LaCroix {
constructor(flavor) {
this.flavor = flavor;
}
}
test('equal-strict',()=> {
expect(new LaCroix('レモン')).toEqual({flavor: 'レモン'});
expect(new LaCroix('レモン')).not.toStrictEqual({flavor: 'レモン'});
});
-
toContain
- 配列内に特定の値が含まれているか確認する
- 文字列内に特定の文字列が含まれているか確認する
test('contein1', ()=> {
const lst = ['abc', 'efg', 'hij'];
expect(lst).toContain('efg');
expect('abcdefghij').toContain('efg');
});
-
toContainEqual
- オブジェクトの配列中に同じ構造で同じ値のオブジェクトが含まれているか確認する
test('contein2', ()=> {
const list = [
{a:1234},
{b:2345, c:'def'},
{c:1111, d: { e:2222}}
];
expect(list).toContainEqual({a:1234});
expect(list).toContainEqual({b:2345, c:'def'});
expect(list).toContainEqual({c:1111, d:{e:2222}});
});
-
toThrow
- 例外が発生することを確認する
- 引数としてエラメッセージの文字、正規表現、エラーオブジェクト、クラスを与えることができる
test.only('throw', ()=> {
expect(()=> {
error_func();
}).toThrow();
expect(()=> {
error_func();
}).toThrow('Error');
expect(()=> {
error_func();
}).toThrow(/rr/);
expect(()=> {
error_func();
}).toThrow(new Error('Error'));
expect(()=> {
error_func();
}).toThrow(Error);
});
テストの同時実行
test.concurrentを使用することでテストを同時に実行することが可能です。
ただし26.4時点で実験的な機能なため以下のような問題が存在しています。
https://github.com/facebook/jest/labels/Area%3A%20Concurrent
以下のコードはconcurrentを使用しないケースと使用したケースの挙動を確認しています。
test('addition of 2 numbers', () => {
console.log('test1');
return new Promise(resolve =>{
setTimeout(function() {
console.log('end test1');
expect(5 + 3).toBe(8);
resolve('done!');
}, 3000);
});
});
test('subtraction 2 numbers', () => {
console.log('test2');
return new Promise(resolve =>{
setTimeout(function() {
console.log('end test2');
expect(5 - 3).toBe(2);
resolve('done!');
}, 1000);
});
});
この実行結果は以下のようになります。
1つ目のテストケースが完了した後に、2つ目のテストケースが完了することがわかります。
以下はconcurrentを使用したケースです。
test.concurrent('addition of 2 numbers', () => {
console.log('test1');
return new Promise(resolve =>{
setTimeout(function() {
console.log('end test1');
expect(5 + 3).toBe(8);
resolve('done!');
}, 3000);
});
});
test.concurrent('subtraction 2 numbers', () => {
console.log('test2');
return new Promise(resolve =>{
setTimeout(function() {
console.log('end test2');
expect(5 - 3).toBe(2);
resolve('done!');
}, 1000);
});
});
この結果より同時にテストケースが動作していることが確認できます。
VueでJestを利用する
前提:
@vue/cli 4.5.6 がインストールされている。
単純な作成例
「vue create」を実行した際にマニュアルで設定を行うと単体テストをjestで行えるように指定できます。
ここでは後からjestを導入することを想定した手順とします。
以下のコマンドを実行することで、vueプロジェクトにjestを使うためのプラグインをインストールしています。
# デフォルトでvueのプロジェクトを作成する
vue create vue_test
cd vue_test
# テストツールのインストール
npm install --save-dev @vue/cli-plugin-unit-jest vue-template-compiler @vue/test-utils
mkdir -p tests/unit
jest用の設定ファイルを作成します。
module.exports = {
preset: '@vue/cli-plugin-unit-jest'
}
tests/unitフォルダにテストコードを追加します。
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
})
cli-plugin-unit-jestプラグインをインストールした後は、vue-cli-serviceコマンドを使用することで単体テストが行えるようになります。
npx vue-cli-service test:unit
スナップショットテスト
jestはスナップショットテストをサポートしており、レンダリングしたHTMLが前回のテスト時と変更されたかをチェックすることができます。
https://jestjs.io/docs/ja/snapshot-testing#inline-snapshots
https://system.blog.uuum.jp/entry/2017/12/12/110000
スナップショットテストをjest+vueで行うにはvue-server-rendererをインストールして、node.jsでVueをレンダリングできるようにします。
npm install --save-dev vue-server-renderer
テストコード
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
import { createRenderer } from 'vue-server-renderer'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
it('snap shot test', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
// コンポーネントをHTML文字列にレンダリング
// https://system.blog.uuum.jp/entry/2017/12/12/110000
const renderer = createRenderer()
renderer.renderToString(wrapper.vm, (err, str) => {
if (err) throw new Error(err)
// 最新のスナップショットと一致するか比較
expect(str).toMatchSnapshot()
})
})
})
このテストコードを実行すると、tests/unitに__snapshots__フォルダが作成されます。
何も変更せずにテストを際実行するとテストは合格します。
そこで、テストコードの以下を修正します。
// 略
const msg = 'new message!!'
// 略
修正後にテストを際実行すると以下のようなエラーが発生します。
もし、このエラーが予定通りの場合はスナップショットを更新してテストを合格にする必要があります。そのためには以下のコマンドを実行します。
npx vue-cli-service test:unit -u
このコマンドを実行後、スナップショットは更新されて、テストが合格するようになります。
この例のようにtoMatchSnapshotを使用すると外部ファイルにスナップショットを保存しますが、toMatchInlineSnapshotを使用することでテストコード中に期待するHTMLを記載することができます。
以下はインラインスナップショットのテストコードのサンプルです。初回実行前には期待値は存在しません。
it("inline snap shot test"", () => {
const msg = "new message!!";
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
});
// コンポーネントをHTML文字列にレンダリング
// https://system.blog.uuum.jp/entry/2017/12/12/110000
const renderer = createRenderer();
renderer.renderToString(wrapper.vm, (err, str) => {
if (err) throw new Error(err);
expect(str).toMatchInlineSnapshot();
});
});
テストを実行するとテストコードは次のように変更されます。
it("inline snap shot test", () => {
const msg = "new message!!";
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
});
// コンポーネントをHTML文字列にレンダリング
// https://system.blog.uuum.jp/entry/2017/12/12/110000
const renderer = createRenderer();
renderer.renderToString(wrapper.vm, (err, str) => {
if (err) throw new Error(err);
// 最新のスナップショットと一致するか比較
expect(str).toMatchInlineSnapshot(`
<div class="hello">
<h1>new message!!</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
`);
});
トラブルシュート
requireのキャッシュをクリアしたい
jest使用中にrequireのキャッシュをクリアしたいケースがある。
require.cacheの内容を削除することで対応できるが、jest使用中は、ここにキャッシュが格納されない。
beforeEachにてresetModulesを実行することで、読み込んでいたrequireのキャッシュを消すことが可能。
beforeEach(() => {
jest.resetModules()
})
依存しているファイルの一部だけモックしたい
/* eslint-disable import/first */
jest.mock('src/utility', () => ({
...(jest.requireActual('src/utility')), // 特定の関数以外は本物を使う
sleep: jest.fn()
}))
import { sleep } from 'src/utility' // モックされた関数
import hoge from './src/hoge.js' // sleepに依存しているモジュールでもsleepはモックされた関数となる
https://stackoverflow.com/questions/39755439/how-to-mock-imported-named-function-in-jest-when-module-is-unmocked
https://jestjs.io/docs/bypassing-module-mocks
いつも動いてるテストが突然不安定になった
jestは規定の動きではキャッシュを保存しています。
このキャッシュを以下のコマンドでクリアします。
npx jest --clearCache
Canvasを使用するテストをしたい
jestの仮想DOMはCanvasをサポートしていないので、getContextがnullになります。
getContextをモックアップする方法もありますが、面倒ならば
jest-canvas-mockを使用しましょう。