Jestのマニュアルモックのドキュメントの和訳
趣味プロダクトでfsのモッキングをする必要が出てきたので https://jestjs.io/docs/en/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
というスコープされたモジュールをモックするには__mocks__/@scope/project-name.js
にファイルを作成し、それにしたがって@scope/
ディレクトリを作成します。
警告: Nodeのコアモジュール(fsやpath)をモックしたい場合は、コアNodeモジュールはデフォルトではモックされないため、明示的に
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')
を明示的に呼び出す必要があります。
注意: 適切にモックするためには、require/import文と同じスコープに
jest.mock('moduleName')
を入れる必要があります。
次の例では、指定したディレクトリ内のすべてのファイルの内容を取得するモジュールを用意しています。この場合、コア(組み込み)fsモジュールを使用しています。
// 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.genMockFromModule('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モジュールであるため、fsモジュールをモックするということを明示的に伝える必要があることに注意してください。
// __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.getMockFromModule
を利用して自動モックを生成し、そのデフォルト動作をオーバーライドしています。これは推奨される方法ですが、完全にオプションです。自動モックをまったく使用したくない場合は、単にモックファイルから独自の関数をエクスポートすることができます。完全な手動モックの欠点の一つは、それらが手動であることです。つまり、モックしているモジュールに変更があった場合にいつでも手動で更新する必要があります。このため、必要に応じて自動モックを使用または拡張することを推奨しています。
マニュアルモックと実物のモジュールを同期させるためには、マニュアルモックの中でrequire.requireActual(moduleName)
関数で本物を呼び出してエクスポートする前にそれらをモックに置き換えるようにすると便利です。
このコードの例は、examples/manual-mocksにあります。
ES Module Importを利用する
ESモジュールのインポートを使用している場合、Importをテストファイルの先頭に置く傾向があります。しかし、JestではモジュールのImportよりも前にモックを使うときがあります。このため、Jestはjest.mock
の呼び出しを自動的にモジュールの先頭(importより前)に持ち込みます。詳細についてはこのリポジトリを参照してくだだい。
JSDOMに実装されていないメソッドのモック
JSDOM(Jestが使用しているDOM実装)に実装されていないメソッドを使用しているコードがあれば、簡単にテストすることはできません。たとえば、window.matchMedia()
の場合JestはTypeError: window.matchMedia is not a function
を返し、テストを正しく実行することはできません。
この場合、テストファイル内のmatchMedia
をモックして問題を解決する必要があります。
window.matchMedia = jest.fn().mockImplementation(query => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
};
});
これは、window.matchMedia()
が呼び出される関数(またはメソッド)をテストする場合に呼び出されます。テストファイルでwindow.matchMedia()
が直接実行された場合、Jestは先程と同様のエラーを返します。この場合の解決策は、手動モックを別のファイルに移動し、テストしたいファイルの前にこれをimportすることです。
import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';
describe('myMethod()', () => {
// Test the method here...
});