Skip to content

Commit

Permalink
[INTERNAL] New dynamic import mock for NodeJS >= 20.6.xx (#663)
Browse files Browse the repository at this point in the history
Fixes: SAP/ui5-tooling#891

Node's module loading has changed since 20.6.xx. As `esmock` is unable
to properly mock dynamic imports within CommonJS modules, we need to use
another alternative for this test. `testdouble` seems to work pretty
well for that case, but for NodeJS versions below 20.6.xx it needs a
`--loader` flag and we already use that for `esmock`.

The solution would be to use the old mocking for older NodeJs versions
and `testdouble` for the ones after 20.6.xx (no flag required here):
https://github.com/testdouble/testdouble.js/blob/main/docs/7-replacing-dependencies.md#how-module-replacement-works-for-es-modules-using-import
  • Loading branch information
d3xter666 authored Nov 6, 2023
1 parent c2e36eb commit 06e79fa
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 10 deletions.
59 changes: 58 additions & 1 deletion npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-ava": "^14.0.0",
"eslint-plugin-jsdoc": "^46.8.2",
"testdouble": "^3.20.0",
"esmock": "^2.5.9",
"execa": "^8.0.1",
"jsdoc": "^4.0.2",
Expand Down
35 changes: 26 additions & 9 deletions test/bin/ui5.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,52 @@ import sinonGlobal from "sinon";
import {fileURLToPath} from "node:url";
import {createRequire, Module} from "node:module";
import chalk from "chalk";
import * as td from "testdouble";

const require = createRequire(import.meta.url);

// Node's module loading has changed since 20.6.xx. As 'esmock' is unable to properly
// mock dynamic imports within CommonJS modules, we need to use another alternative for this test.
// 'testdouble' seems to work pretty well for that case, but for NodeJS versions below 20.6.xx it needs
// a `--loader` flag and we already use that for 'esmock'.
// The solution would be to use the old mocking for older NodeJs versions and
// 'testdouble' for the ones after 20.6.xx (no flag required here).
const [nodeMajorVer, NodeMinorVer] = process.versions.node.split(".").map(Number);
const useTestdoubleMock = nodeMajorVer > 20 || (nodeMajorVer === 20 && NodeMinorVer >= 6);

const globalSinonSandbox = sinonGlobal.createSandbox();
const importLocalStub = globalSinonSandbox.stub();

test.before(() => {
// Mock dependencies
// NOTE: esmock is not able to properly mock dynamic imports within a CJS script.
// Also it seems that the stub is cached somewhere else, so beforeEach/afterEach
// can't be used to renew it. But #reset takes care of resetting the stub between tests

const importLocalModule = new Module("import-local");
importLocalModule.exports = importLocalStub;
require.cache[require.resolve("import-local")] = importLocalModule;
if (useTestdoubleMock === false) { // Legacy (manual) mocking
// Mock dependencies
// NOTE: esmock is not able to properly mock dynamic imports within a CJS script.
// Also it seems that the stub is cached somewhere else, so beforeEach/afterEach
// can't be used to renew it. But #reset takes care of resetting the stub between tests

const importLocalModule = new Module("import-local");
importLocalModule.exports = importLocalStub;
require.cache[require.resolve("import-local")] = importLocalModule;
}
});

test.beforeEach((t) => {
test.beforeEach(async (t) => {
const sinon = t.context.sinon = sinonGlobal.createSandbox();
t.context.consoleLogStub = sinon.stub(console, "log");
t.context.consoleInfoStub = sinon.stub(console, "info");
t.context.originalArgv = process.argv;
process.env.UI5_CLI_TEST_BIN_RUN_MAIN = "false"; // prevent automatic execution of main function

if (useTestdoubleMock === true) { // NodeJS versions 20.6 and above
await td.replaceEsm("import-local", {default: importLocalStub});
}
});

test.afterEach.always((t) => {
process.argv = t.context.originalArgv;
t.context.sinon.restore();
globalSinonSandbox.reset();
useTestdoubleMock && td.reset();

// Allow re-requiring the module
delete require.cache[require.resolve("../../bin/ui5.cjs")];
Expand Down

0 comments on commit 06e79fa

Please sign in to comment.