Facebookがオープンソースとして開発しているJestを利用して、ユニットテスト行う方法について確認します。導入方法、主なMatcher、モックの利用方法など取り上げます。
Jestとは
Facebookがオープンソースとして開発している ユニットテストツール
です。
ユニットテストツールはJest以外にも以下のようなツールが存在します。
分類 | 概要 | 例 |
---|---|---|
テストランナー | テスト実行環境、検証結果のレポート機能などを提供 | Karma |
テストフレームワーク | describe it などテストの構造を作る機能を提供 | Mocha |
アサーション | テスト結果が期待通りであるか判定する機能を提供 | Chai |
テストユーティリティ | モック、スタブなどの機能を提供 | Sinon |
Jestは上記機能をオールインワンで提供しているので、導入の負担が低いのが魅力です。
簡単なテストで動作確認
まず、簡単なテストをJestで実行させるところまで確認します。
プロジェクト作成
パッケージ管理に yarn
を利用します。 yarn init
で package.json
を作成します。
$ yarn init
$ cat package.json
{
"name": "test1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT"
}
Jestをインストール
$ yarn add --dev jest
Jestがインストールされました。
$ cat package.json
{
"name": "test1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"jest": "^23.6.0"
}
}
テスト対象ファイル実装
sum.js
というファイルを作成し、以下処理を記述します。
function sum(a, b) {
return a + b
}
module.exports = sum
テストコード実装
sum.test.js
というファイルを作成し、以下処理を記述します。
const sum = require('./sum')
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})
以下の操作を行なっています。
- テスト対象ファイルを読み込み
test関数
- 第1引数にテストの概要を記述
- 第2引数にテストを記述
expect関数
- 引数にテスト対象の処理を記述
- マッチャー(
toBe
toEqual
など)で期待する動作を検証
ここではtest関数
を利用しましたが、it関数
でも同じ動作をします。
ここでは利用しませんでしたが、describe関数
を利用すると複数のテストをグルーピングできます。テストのカテゴライズができるので、テストの管理に役立ちます。
ファイル構成
ここまでの作業で、以下のようなファイル構成になりました。
.
├── node_modules/
├── package.json
├── sum.js
├── sum.test.js
└── yarn.lock
テスト実行
yarnコマンド
経由でテストを実行できるように、package.json
の scripts
を以下のように記述します。
$ cat package.json
{
"name": "test1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"jest": "^23.6.0"
},
"scripts": {
"test": "jest"
}
}
テストを実行します。
$ yarn test
yarn run v1.9.4
$ jest
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (6ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.227s
Ran all test suites.
✨ Done in 3.50s.
簡単なテストではありましたが、Jestを利用してテストを実行することができました。
Jestコマンドのオプション
coverage
coverageオプション
を利用するとテストカバレッジを確認できます。
$ yarn test --coverage
yarn run v1.9.4
$ jest --coverage
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (12ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
sum.js | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.319s
Ran all test suites.
✨ Done in 3.59s.
coverageオプション
を利用した場合、テストを実行すると coverageフォルダ
が生成されます。
coverage/lcov-report/index.html
をブラウザで開くと詳しいカバレッジ情報を確認できます。
$ open coverage/lcov-report/index.html
watch, watchAll
watchオプション
watchAllオプション
を利用すると、ファイルの変更を監視してテストを実行してくれます。
その他
下記ページにてコマンドオプションについて確認できます。
https://jestjs.io/docs/en/cli
主なMatcher
利用頻度の高いMatcherを紹介します。
it('matchers', () => {
// 等しい
expect(1 + 5).toBe(6)
// notで「~でない」の判定ができます。
expect(1 + 5).not.toBe(7)
// toBeは厳密な等価性を判定するので、オブジェクトの場合はtoEqualを利用します。
expect({ a: 100 }).toEqual({ a: 100 })
// 大きい
expect(4).toBeGreaterThan(3)
expect(4).toBeGreaterThanOrEqual(4)
// 小さい
expect(4).toBeLessThan(5)
expect(4).toBeLessThanOrEqual(4)
// null, true, false
expect(null).toBeNull()
expect(true).toBeTruthy()
expect(false).toBeFalsy()
// 文字列
expect('abcdefg').toMatch(/bc/)
expect('abcdefg').not.toMatch(/bd/)
// 配列
expect([1, 2, 3]).toContain(2)
// 例外
const xxx = () => {
throw new Error('error message xxx');
}
expect(xxx).toThrow('error message xxx');
expect(xxx).toThrow(/.*xxx$/);
})
下記ページでより詳しい使い方を確認できます。
- 代表的なものだけ確認
- 全てのMatcherを確認
モックの利用方法
モックの利用方法を紹介します。
関数のモック
関数をモックに差し替えてみます。
const myMethod = (cnt, callback) => {
let total
while (cnt) {
total = callback(cnt)
cnt--
}
return total
}
it('mock', () => {
// arrange
const mockCallback = jest.fn(x => x * 2)
// act
myMethod(3, mockCallback)
// assert
// モックメソッドが3回呼ばれたこと
expect(mockCallback.mock.calls.length).toBe(3)
// モックメソッドが受け取った引数
expect(mockCallback.mock.calls[0][0]).toBe(3) // 1回目 第1引数
expect(mockCallback.mock.calls[1][0]).toBe(2) // 2回目 第1引数
expect(mockCallback.mock.calls[2][0]).toBe(1) // 3回目 第1引数
// モックメソッドの戻り値
expect(mockCallback.mock.results[0].value).toBe(6) // 1回目
expect(mockCallback.mock.results[1].value).toBe(4) // 2回目
expect(mockCallback.mock.results[2].value).toBe(2) // 3回目
})
クラスのモック
ClassAとClassBを用意します。
export default class ClassA {
sum(x, y) {
return x + y
}
}
import ClassA from './class-a'
export default class ClassB {
constructor() {
this.classA = new ClassA()
}
total(x, y, z) {
return this.classA.sum(x, y) * z
}
}
ClassAをモックに差し替えてみます。
import ClassA from './class-a'
import ClassB from './class-b'
// 自動モック
jest.mock('./class-a')
describe('ClassA', () => {
beforeEach(() => {
ClassA.mockClear()
})
it('constructor()', () => {
expect(ClassA).not.toHaveBeenCalled()
new ClassB()
expect(ClassA).toHaveBeenCalledTimes(1)
})
describe('total()', () => {
it('mockReturnValue', () => {
// Arrange
ClassA.prototype.sum = jest.fn().mockReturnValue(10)
const classB = new ClassB()
// Act
const result = classB.total(1, 2, 3)
// Assert
expect(result).toBe(30) // 10[mockReturnValueの引数] * 3
expect(ClassA.prototype.sum).toHaveBeenCalledTimes(1)
})
it('mockImplementationOnce', () => {
// Arrange
ClassA.prototype.sum = jest.fn().mockImplementationOnce((x, y) => {
expect(x).toBe(1)
expect(y).toBe(2)
return 100
})
const classB = new ClassB()
// Act
const result = classB.total(1, 2, 3)
// Assert
expect(result).toBe(300) // 100[mockImplementationOnceの戻り値] * 3
expect(ClassA.prototype.sum).toHaveBeenCalledWith(1, 2)
})
})
})
モック関連で利用する主なメソッド
jest.fn()
- mock functionを生成
jest.mock()
- モジュール、クラスの自動モック
jest.fn().mockReturnValue()
- 呼ばれたときに代わりに返す値を設定
jest.fn().mockImplementation()
- 呼ばれたとき代わりに実行させる処理を設定
- Matchers
- 呼ばれたことを検証
toHaveBeenCalled()
toHaveBeenCalledTimes()
toHaveBeenCalledWith()
- 呼ばれたことを検証
詳しい利用方法は以下ページで確認できます。
- https://jestjs.io/docs/en/mock-functions
- モジュールのモック
- https://jestjs.io/docs/en/mock-functions#mocking-modules
- axiosのAPI呼び出しをモックに差し替えるなどの利用方法が解説されています。
- クラスのモック
設定調整
調整方法
以下の方法で設定を調整できます。
package.jsonファイル
に設定を記述jest.config.jsファイル
を作成して、設定を記述- テスト実行時にオプションで設定を調整
詳しい設定方法などは以下ページで確認できます。
https://jestjs.io/docs/en/configuration
testRegexでテストファイルを調整
Jestはデフォルトだと以下のファイルをテストファイルとみなします。
*.test.js
*.spec.js
__tests__
ディレクトリ以下のファイル
変更したい場合は、 testRegex
の設定を調整します。
https://jestjs.io/docs/en/configuration#testregex-string