はじめに
以下の記事で、Node.js Test Runnerで、モジュールのサブセットをモックする方法を書きました。
しかし、サブセットでないモジュールのモックもしたいときがあるなと思い調べたところ、モジュールモックをできる簡易的なヘルパーができたので紹介します。
なお、今回試すのはCJSです。
ヘルパーを使ってテストする
サブセットでないモジュール(正しい名前がわからない)と言っているのは以下のようなものです。
module.exports = (a, b) => {
return a + b;
};
今回は、 node-fetch
で試します。
テストしたいものは以下のgetExampleBody
関数です。
const fetch = require("node-fetch");
exports.getExampleBody = async () => {
const response = await fetch("https://example.com");
const body = await response.text();
return body;
};
このgetExampleBody
をテストする際に、fetch
でのリクエストをさせたくないため、fetch
関数をモックしたいです。
の記事で書いたように、サブセットのモジュールは mock.method
でモックができるのですが、今回の場合はできません。
そこで思いついたのが、モジュールのロードに介入して、モック関数を返す方法です。
実際に書いたヘルパーは以下です。
const Module = require("node:module");
const mocks = {};
const originalJsLoader = Module._extensions[".js"];
Module._extensions[".js"] = function (mod, filename) {
if (mocks[filename]) {
mod.exports = mocks[filename];
} else {
originalJsLoader(mod, filename);
}
};
exports.mockModule = (name, fn) => {
const filename = require.resolve(name);
mocks[filename] = fn;
delete require.cache[filename];
return fn;
};
使い方は簡単で、mockModule
の第一引数にモジュール名、第二引数にモック関数を渡すだけです。
このヘルパーを使って書いたテストコードは以下になります。
const { test, mock } = require("node:test");
const assert = require("node:assert");
const { mockModule } = require("./module-mock-helper");
const fetch = require("node-fetch");
const mockFetch = mockModule(
"node-fetch",
mock.fn(fetch, async () => {
return {
text: async () => {
return "sample body";
},
};
}),
);
const { getExampleBody } = require("./index");
test("getExampleBody with mock", async () => {
const body = await getExampleBody();
assert.equal(body, "sample body");
assert.equal(mockFetch.mock.callCount(), 1);
});
mockModule
の仕組みは、モジュールのロードに介入し、モック関数を返すようにしています。
mockModule
で渡されたモジュール名からパスを取得し、そのパスをkeyとして渡されたモック関数を記憶しておきます。
そして、Module._extensions
をオーバーライドし、記憶しているモジュールをロードしようとした際にモック関数を返すようにしています。
さいごに
無事、ビルトインモジュールだけでモジュールモックができました。
ただ、Native ESMの場合のモジュールロード周りがわからないので、次回挑戦するならESMで挑戦してみようと思います。
Refs
- Read Node.js Source Code to Deeply Understand the CJS Module System - Alibaba Cloud Community
- requireの仕組み - ぶれすとつーる
- javascript - How to stub require() / expect calls to the "root" function of a module?- Stack Overflow
- Modules: CommonJS modules | Node.js v20.4.0 Documentation
- node/lib/internal/modules/cjs/loader.js at v20.4.0 · nodejs/node · GitHub
- NodeJS Modules - How to Load a Module with require()
- Node.js の require はキャッシュされるよ - 🐾 Nekonote