diff --git a/doc/api/test.md b/doc/api/test.md index fe70e210b77e33..d47e6208325499 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..1a1ef67632acd9 100644 --- a/test/parallel/test-runner-module-mocking.js +++ b/test/parallel/test-runner-module-mocking.js @@ -365,6 +365,34 @@ 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); + }); + + await t.test('throws without appropriate import attributes', async (t) => { + const fixturePath = fixtures.path('module-mocking', 'basic.json'); + const fixture = pathToFileURL(fixturePath); + + const defaultExport = { qux: 'zed' }; + t.mock.module(fixture, { defaultExport }); + + await assert.rejects(() => import(fixture), /import attribute/); + }); +}); + 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');