メインコンテンツへスキップ
Version: Next

マニュアルモック

マニュアルモックはモックデータを返す機能をスタブするために使用します。 例えば、ウェブサイトやデータベースのような外部リソースにアクセスする代わりに、偽のデータが使えるマニュアルモックが欲しいと考えるでしょう。 これによりテストは高速で信頼性の高いものになります。

ユーザーモジュールのモック

マニュアルモックは モジュールのディレクトリ直下の__mocks__/ サブディレクトリにモックモジュールを作成することで定義します。 例えばmodels ディレクトリに user と呼ばれるモジュールをモックを作成するには、 models/__mocks__ ディレクトリにuser.js というファイルを作成して配置します。

caution

The __mocks__ folder is case-sensitive, so naming the directory __MOCKS__ will break on some systems.

note

When we require that module in our tests (meaning we want to use the manual mock instead of the real implementation), explicitly calling jest.mock('./moduleName') is required.

Node モジュールのモック

モックしようとしているモジュールが Node モジュール (例: lodash) の場合、(ルートフォルダをプロジェクトルート以外に設定していない限り)node_modules ディレクトリと同階層の __mocks__ の中にモックを置くことで、モジュールは自動的にモックされます。 明示的に jest.mock('module_name') を呼び出す必要はありません。

Scoped modules (also known as scoped packages) can be mocked by creating a file in a directory structure that matches the name of the scoped module. たとえば、@scope/project-name という名前のスコープモジュールをモックしたいなら、@scope/ ディレクトリを作成するのに対応して、__mocks__/@scope/project-name.js というファイルを作成します。

caution

If we want to mock Node's built-in modules (e.g.: fs or path), then explicitly calling e.g. jest.mock('path') is required, because built-in modules are not mocked by default.

コーディング例

.
├── config
├── __mocks__
│   └── fs.js
├── models
│   ├── __mocks__
│   │   └── user.js
│   └── user.js
├── node_modules
└── views

与えられたモジュールにマニュアルモックが存在する場合、Jestのモジュールシステムはjest.mock('moduleName')によってモックモジュールが明示的に呼び出された時に使用します。 しかし、automocktrue にセットすると、たとえ jest.mock('moduleName') が呼ばれなくても、自動的に作られたモックの代わりに、マニュアルモックの実装が使用されます。 この振る舞いを止めさせるには、本物のモジュールを使用するべきテストの中で jest.unmock('moduleName')を明示的に呼び出す必要があります。

info

In order to mock properly, Jest needs jest.mock('moduleName') to be in the same scope as the require/import statement.

以下は指定されたディレクトリ内の全てのファイルの要約を提供するモジュールの分かりやすい例です。 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;

さて、テストを書きましょう。 In this case jest.mock('fs') must be called explicitly, because fs is Node’s built-in module:

__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) 関数で実物を呼び出しておき、エクスポートする前にそれらをモック関数に置き換えるようにすると便利です。

The code for this example is available at examples/manual-mocks.

ES module importを利用する

ES module importsを使用している場合、通常はテストファイルの先頭でimport宣言を書くことが多いでしょう。 しかしモジュールがそれらを使用するのに先立ち、Jestにモックを使用するよう指示する必要があります。 このため、Jestは自動的にjest.mockコールを自動的にモジュールの先頭に(importを行う前に)移動します。 これについての詳細および実例については、 このリポジトリを参照して下さい。

caution

jest.mock calls cannot be hoisted to the top of the module if you enabled ECMAScript modules support. The ESM module loader always evaluates the static imports before executing code. See ECMAScriptModules for details.

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...
});