From 896149620850398e7a368e3834ac41cfc20d8d34 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:46:11 +0200 Subject: [PATCH 1/4] test_runner: support mocking json modules --- doc/api/test.md | 13 +++++++++---- lib/internal/test_runner/mock/loader.js | 15 +++++++++++---- lib/internal/test_runner/mock/mock.js | 9 ++++++++- test/fixtures/module-mocking/basic.json | 1 + test/parallel/test-runner-module-mocking.js | 18 ++++++++++++++++++ 5 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/module-mocking/basic.json diff --git a/doc/api/test.md b/doc/api/test.md index fe70e210b77e33..8b237e2902d2b3 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -2168,6 +2168,11 @@ test('spies on an object method', (t) => { added: - v22.3.0 - v20.18.0 +changes: + - version: + - REPLACEME + pr-url: https://github.com/nodejs/node/pull/58007 + description: Support JSON modules --> > Stability: 1.0 - Early development @@ -2191,10 +2196,10 @@ added: mock will throw an exception when used as a CJS or builtin module. * Returns: {MockModuleContext} An object that can be used to manipulate the mock. -This function is used to mock the exports of ECMAScript modules, CommonJS -modules, and Node.js builtin modules. Any references to the original module -prior to mocking are not impacted. In order to enable module mocking, Node.js must -be started with the [`--experimental-test-module-mocks`][] command-line flag. +This function is used to mock the exports of ECMAScript modules, CommonJS modules, JSON modules, and +Node.js builtin modules. Any references to the original module prior to mocking are not impacted. In +order to enable module mocking, Node.js must be started with the +[`--experimental-test-module-mocks`][] command-line flag. The following example demonstrates how a mock is created for a module. diff --git a/lib/internal/test_runner/mock/loader.js b/lib/internal/test_runner/mock/loader.js index bfdfe93741c2cc..6739bded60ff44 100644 --- a/lib/internal/test_runner/mock/loader.js +++ b/lib/internal/test_runner/mock/loader.js @@ -118,10 +118,17 @@ async function load(url, context, nextLoad) { // Treat builtins as commonjs because customization hooks do not allow a // core module to be replaced. // Also collapse 'commonjs-sync' and 'require-commonjs' to 'commonjs'. - const format = ( - original.format === 'builtin' || - original.format === 'commonjs-sync' || - original.format === 'require-commonjs') ? 'commonjs' : original.format; + let format = original.format; + switch (original.format) { + case 'builtin': // Deliberate fallthrough + case 'commonjs-sync': // Deliberate fallthrough + case 'require-commonjs': + format = 'commonjs'; + break; + case 'json': + format = 'module'; + break; + } const result = { __proto__: null, diff --git a/lib/internal/test_runner/mock/mock.js b/lib/internal/test_runner/mock/mock.js index 99fe1baebb771b..178da37efc16c8 100644 --- a/lib/internal/test_runner/mock/mock.js +++ b/lib/internal/test_runner/mock/mock.js @@ -70,7 +70,14 @@ const kMockUnknownMessage = 3; const kWaitTimeout = 5_000; const kBadExportsMessage = 'Cannot create mock because named exports ' + 'cannot be applied to the provided default export.'; -const kSupportedFormats = ['builtin', 'commonjs', 'module', 'module-typescript', 'commonjs-typescript']; +const kSupportedFormats = [ + 'builtin', + 'commonjs-typescript', + 'commonjs', + 'json', + 'module-typescript', + 'module', +]; let sharedModuleState; class MockFunctionContext { diff --git a/test/fixtures/module-mocking/basic.json b/test/fixtures/module-mocking/basic.json new file mode 100644 index 00000000000000..2393cd01d4bfa0 --- /dev/null +++ b/test/fixtures/module-mocking/basic.json @@ -0,0 +1 @@ +{"foo":"bar"} diff --git a/test/parallel/test-runner-module-mocking.js b/test/parallel/test-runner-module-mocking.js index f2db563f959e55..e84e9793010283 100644 --- a/test/parallel/test-runner-module-mocking.js +++ b/test/parallel/test-runner-module-mocking.js @@ -365,6 +365,24 @@ test('ESM mocking with namedExports option', async (t) => { }); }); +test('JSON mocking', async (t) => { + await t.test('with defaultExport', async (t) => { + const fixturePath = fixtures.path('module-mocking', 'basic.json'); + const fixture = pathToFileURL(fixturePath); + const { default: original } = await import(fixture, { with: { type: 'json' } }); + + assert.deepStrictEqual(original, { foo: 'bar' }); + + const defaultExport = { qux: 'zed' }; + + t.mock.module(fixture, { defaultExport }); + + const { default: mocked } = await import(fixture, { with: { type: 'json' } }); + + assert.deepStrictEqual(mocked, defaultExport); + }); +}); + test('modules cannot be mocked multiple times at once', async (t) => { await t.test('CJS', async (t) => { const fixture = fixtures.path('module-mocking', 'basic-cjs.js'); From 17764f7c84474aed28a2498bea3eddc29805c68d Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:03:13 +0200 Subject: [PATCH 2/4] fixup!: de-lint --- doc/api/test.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/test.md b/doc/api/test.md index 8b237e2902d2b3..d47e6208325499 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -2172,7 +2172,7 @@ changes: - version: - REPLACEME pr-url: https://github.com/nodejs/node/pull/58007 - description: Support JSON modules + description: Support JSON modules. --> > Stability: 1.0 - Early development From 32a4f02d53fb0701800271d7fe711741d2694277 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:08:41 +0200 Subject: [PATCH 3/4] fixup!: add test case to ensure import attributes are still required --- test/parallel/test-runner-module-mocking.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/parallel/test-runner-module-mocking.js b/test/parallel/test-runner-module-mocking.js index e84e9793010283..c7b17609a05c94 100644 --- a/test/parallel/test-runner-module-mocking.js +++ b/test/parallel/test-runner-module-mocking.js @@ -381,6 +381,20 @@ test('JSON mocking', async (t) => { assert.deepStrictEqual(mocked, defaultExport); }); + + await t.test('throws without appropriate import attributes', async (t) => { + const fixturePath = fixtures.path('module-mocking', 'basic.json'); + const fixture = pathToFileURL(fixturePath); + const { default: original } = await import(fixture, { with: { type: 'json' } }); + + assert.deepStrictEqual(original, { foo: 'bar' }); + + const defaultExport = { qux: 'zed' }; + + t.mock.module(fixture, { defaultExport }); + + assert.rejects(() => import(fixture), /import attribute/); + }); }); test('modules cannot be mocked multiple times at once', async (t) => { From 4e1ed7a4e29f8751f994114970f9fcaf0543bcfe Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:28:50 +0200 Subject: [PATCH 4/4] fixup!: don't pre-load original before testing rejection Co-authored-by: Antoine du Hamel --- test/parallel/test-runner-module-mocking.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/parallel/test-runner-module-mocking.js b/test/parallel/test-runner-module-mocking.js index c7b17609a05c94..1a1ef67632acd9 100644 --- a/test/parallel/test-runner-module-mocking.js +++ b/test/parallel/test-runner-module-mocking.js @@ -385,15 +385,11 @@ test('JSON mocking', async (t) => { await t.test('throws without appropriate import attributes', async (t) => { const fixturePath = fixtures.path('module-mocking', 'basic.json'); const fixture = pathToFileURL(fixturePath); - const { default: original } = await import(fixture, { with: { type: 'json' } }); - - assert.deepStrictEqual(original, { foo: 'bar' }); const defaultExport = { qux: 'zed' }; - t.mock.module(fixture, { defaultExport }); - assert.rejects(() => import(fixture), /import attribute/); + await assert.rejects(() => import(fixture), /import attribute/); }); });