Manual Mocks
マニュアルモックはモックデータを返す機能をスタブするために使用します。 例えば、ウェブサイトやデータベースのような外部リソースにアクセスする代わりに、偽のデータが使えるマニュアルモックが欲しいと考えるでしょう。 これによりテストは高速で信頼性の高いものになります。
ユーザーモジュールのモック
マニュアルモックは モジュールのディレクトリ直下の__mocks__/
サブディレクトリにモックモジュールを作成することで定義します。 例えばmodels
ディレクトリに user
と呼ばれるモジュールをモックを作成するには、 models/__mocks__
ディレクトリにuser.js
というファイルを作成して配置します。 __mocks__
のフォルダ名は小文字であることが必要で、ディレクトリ名を __MOCKS__
にすると一部のシステムが壊れる場合があることに注意してください。
テストの中でそのモジュールが必要な場合には、明示的に
jest.mock('./moduleName')
を呼ぶことが 必須です。
Node モジュールのモック
モックしようとしているモジュールが Node モジュール (例: lodash
) の場合、(ルート
フォルダをプロジェクトルート以外に設定していない限り)node_modules
ディレクトリと同階層の __mocks__
の中にモックを置くことで、モジュールは自動的にモックされます。 明示的に jest.mock('module_name')
を呼び出す必要はありません。
名前と一致するディレクトリ構造にファイルを作成することで、スコープ付きモジュールをモックできます。 たとえば、@scope/project-name
という名前のスコープモジュールをモックしたいなら、@scope/
ディレクトリを作成するのに対応して、__mocks__/@scope/project-name.js
というファイルを作成します。
警告: Node の core モジュール (例:
fs
やpath
) をモックしたい場合は、デフォルトではモックされないため、たとえばjest.mock('path')
と明示的に呼び出すことが必要です。
コーディング例
.
├── config
├── __mocks__
│ └── fs.js
├── models
│ ├── __mocks__
│ │ └── user.js
│ └── user.js
├── node_modules
└── views
与えられたモジュールにマニュアルモックが存在する場合、Jestのモジュールシステムはjest.mock('moduleName')
によってモックモジュールが明示的に呼び出された時に使用します。 しかし、automock
を true
にセットすると、たとえ jest.mock('moduleName')
が呼ばれなくても、自動的に作られたモックの代わりに、マニュアルモックの実装が使用されます。 この振る舞いを止めさせるには、本物のモジュールを使用するべきテストの中で jest.unmock('moduleName')
を明示的に呼び出す必要があります。
注意: モックを正しく行うには、Jest は
require/import
宣言と同じスコープとなるよう、jest.mock('moduleName')
が必要です。
Here's a contrived example where we have a module that provides a summary of all the files in a given directory. In this case, we use the core (built in) fs
module.
// FileSummarizer.js
'use strict';
const fs = require('fs');
function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}
exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;
テスト内で実際にディスクアクセスを発生させるのは避けたいので(それはとても時間がかかり不安定です)、自動モックを拡張して fs
モジュールのマニュアルモックを作成します。 作成するマニュアルモックがテストに組み込めるよう、独自バージョンの fs
APIを実装します。
// __mocks__/fs.js
'use strict';
const path = require('path');
const fs = jest.createMockFromModule('fs');
// This is a custom function that our tests can use during setup to specify
// what the files on the "mock" filesystem should look like when any of the
// `fs` APIs are used.
let mockFiles = Object.create(null);
function __setMockFiles(newMockFiles) {
mockFiles = Object.create(null);
for (const file in newMockFiles) {
const dir = path.dirname(file);
if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
}
}
// A custom version of `readdirSync` that reads from the special mocked out
// file list set via __setMockFiles
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}
fs.__setMockFiles = __setMockFiles;
fs.readdirSync = readdirSync;
module.exports = fs;
さて、テストを書きましょう。fs
モジュールは コアなNode モジュールであるため、明示的にモックしてほしいと伝えることが必要なことに注意してください。
// __tests__/FileSummarizer-test.js
'use strict';
jest.mock('fs');
describe('listFilesInDirectorySync', () => {
const MOCK_FILE_INFO = {
'/path/to/file1.js': 'console.log("file1 contents");',
'/path/to/file2.txt': 'file2 contents',
};
beforeEach(() => {
// Set up some mocked out file info before each test
require('fs').__setMockFiles(MOCK_FILE_INFO);
});
test('includes all files in the directory in the summary', () => {
const FileSummarizer = require('../FileSummarizer');
const fileSummary = FileSummarizer.summarizeFilesInDirectorySync(
'/path/to',
);
expect(fileSummary.length).toBe(2);
});
});
ここに示されているモックの例では、自動モックを生成するために jest.createMockFromModule
を使用し、デフォルトの動作を上書きします。 これはお勧めの方法ですが、必ずしもこのようにする必要はありません。 自動モックを全く使いたくない場合は、単純にモックファイルから独自に作成した関数をエクスポートすれば良いだけです。 完全に手作業でモックを作成することの欠点の1つは、手作業であること - つまりモックする対象のモジュールが変更するごとに手動で更新しなければならないことです。 このため、目的に沿うのなら自動モックをそのまま使うか拡張することをお勧めします。
マニュアルモックと実部を確実に同期しておくには、マニュアルモックの中でjest.requireActual(moduleName)
関数で実物を呼び出しておき、エクスポートする前にそれらをモック関数に置き換えるようにすると便利です。
このコードの例はexamples/manual-mocksで確認できます。
ES module importを利用する
ES module importsを使用している場合、通常はテストファイルの先頭でimport
宣言を書くことが多いでしょう。 しかしモジュールがそれらを使用するのに先立ち、Jestにモックを使用するよう指示する必要があります。 このため、Jestは自動的にjest.mock
コールを自動的にモジュールの先頭に(importを行う前に)移動します。 これについての詳細および実例については、 このリポジトリを参照して下さい。
JSDOM に実装されていないメソッドのモック
JSDOM (Jest で使用されている DOM 実装) がまだ実装していないメソッドを使用するコードがある場合、テストは容易ではありません。 例えば、 window.matchMedia()
の場合です。 Jest は TypeError: window.matchMedia is not a function
を返し、テストを正しく実行しません。
この場合、テストファイル内の matchMedia
をモックすることで問題が解決します。
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
これは、 window.matchMedia()
がテストで呼び出された関数 (またはメソッド) で使用されている場合に機能します。 window.matchMedia()
がテスト対象のファイル内で直接実行された場合、Jest は同じエラーを報告します。 この場合の解決方法は、マニュアルモックを別のファイルに移動し、テスト対象ファイルのテスト前に読み込むことです:
import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';
describe('myMethod()', () => {
// Test the method here...
});