UNPKG

cjs-mock

Version:

NodeJS module mocking for CJS (CommonJS) modules for unit testing purposes.

101 lines 4.73 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.mock = void 0; /* eslint-disable no-underscore-dangle */ /* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-require-imports */ const path_1 = __importDefault(require("path")); const callsites_1 = __importDefault(require("callsites")); const colorette_1 = require("colorette"); const Module = require('module'); const depsToMock = new Map(); function debug(msg) { if (!process.env.CJS_MOCK_DEBUG) return; console.log('CJS_MOCK_DEBUG: ', msg); } Module.prototype.require = new Proxy(Module.prototype.require, { apply(target, thisArg, argumentsList) { const [name] = argumentsList; // eslint-disable-next-line no-underscore-dangle const absolutePath = Module._resolveFilename(name, thisArg); const mock = depsToMock.get(absolutePath); // Only replace if the module is a direct dependency of the caller if (mock && mock.parentPath === thisArg.filename) { debug(`require(): ${(0, colorette_1.green)('REPLACING WITH MOCK')} ${(0, colorette_1.bold)(name)} [${absolutePath}] ${getStackTrace()}`); depsToMock.delete(absolutePath); return mock.mockReturnValue; } debug(`require(): ${(0, colorette_1.bold)(name)} [${absolutePath}] ${getStackTrace()}`); return Reflect.apply(target, thisArg, argumentsList); }, }); /** * Resolves a module path to an absolute path * @param modulePath - The module path to resolve * @param dir - The directory to resolve the module path in * @param parentModule - The parent module to resolve the module path in * @returns The absolute path of the module */ function resolve(modulePath, dir, parentModule) { // if path starts with ., then it's relative if (modulePath.slice(0, 1) === '.') { const resolvedAbsPath = path_1.default.resolve(dir, modulePath); return Module._resolveFilename(resolvedAbsPath, parentModule); } return Module._resolveFilename(modulePath, parentModule); } function registerDepsToReplace(mockModules, dir, parentModule, targetModulePath) { Object.entries(mockModules).forEach((mockModule) => { const [modulePath, mockReturnValue] = mockModule; const absolutePath = resolve(modulePath, dir, parentModule); debug(`will replace: ${modulePath} [${absolutePath}]`); depsToMock.set(absolutePath, { dependencyPath: modulePath, mockReturnValue, parentPath: targetModulePath, // This is the module being mocked }); }); } function mock(modulePath, deps = {}) { var _a, _b; const callerFile = (0, callsites_1.default)()[1].getFileName(); const callerModule = (_a = Object.values(Module._cache).find((mod) => mod.filename === callerFile)) !== null && _a !== void 0 ? _a : (_b = module.parent) === null || _b === void 0 ? void 0 : _b.parent; // this fallback assumes the caller is two levels up (mock.ts -> index.ts -> caller) const dir = path_1.default.dirname(callerFile); const absolutePath = resolve(modulePath, dir, callerModule); const moduleDir = path_1.default.dirname(absolutePath); debug(`mocking: ${modulePath} [${absolutePath}]`); // Pass the absolutePath as the targetModulePath registerDepsToReplace(deps, moduleDir, callerModule, absolutePath); delete require.cache[absolutePath]; // require the module that we're mocking const mod = require(absolutePath); // make sure there are no unused mocks if (depsToMock.size) { throw new Error(`The following imports were not found in ${modulePath}: ${[...depsToMock.values()].map((mock) => mock.dependencyPath).join(', ')}`); } // make sure this is not cached either, especially as it contains mocks that we don't want to keep around delete require.cache[absolutePath]; return mod; } exports.mock = mock; function getStackTrace() { const trace = (0, callsites_1.default)() .slice(1) .filter((callsite) => { const file = callsite.getFileName(); // filter out internal, node_modules, and cjs-mock files return file && !file.includes('internal') && !file.includes('node_modules') && !file.includes('cjs-mock'); }) .map((callsite) => (0, colorette_1.gray)(` at ${callsite.getFileName()} ${callsite.getLineNumber()}:${callsite.getColumnNumber()}`)) .join('\n'); return trace ? `\n${trace}` : ''; } //# sourceMappingURL=mock.js.map