Skip to content

Test runner module mocking doesn't work for CJS if package exports are specified #58231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
sloops77 opened this issue May 8, 2025 · 3 comments

Comments

@sloops77
Copy link

sloops77 commented May 8, 2025

Version

22.15

Platform

Darwin Andress-MacBook-Pro.local 24.4.0 Darwin Kernel Version 24.4.0: Fri Apr 11 18:33:47 PDT 2025; root:xnu-11417.101.15~117/RELEASE_ARM64_T6000 arm64

Subsystem

node:mock

What steps will reproduce the bug?

The issue is for packages that expose esm module and cjs main. See Repo: https://github.com/sloops77/node-mock-bug for more examples

Rather than create my own package the examples below use the nanoid 3.x package. The linked repo also shows that it is reproducible for the ethers package (which also exposes module and main).

// snippet of package.json from nanoid
{
  "name": "nanoid",
  "version": "3.3.11",
  "description": "A tiny (116 bytes), secure URL-friendly unique string ID generator",
  "type": "module",
  "main": "index.cjs",
  "module": "index.js",
  ...
// test.js
const {describe, it, mock} = require("node:test");
const assert = require("node:assert");

mock.module('nanoid', {
  namedExports: {
    nanoid() {
      return '1234'
    }
  },
});


test('mocking problem in cjs', () => {
  const {nanoid} = require('nanoid')
  assert.strictEqual(nanoid(), '1234'); // fails test because cjs is not mocked
})

test('mocking works for esm', async () => {
  const {nanoid} = await import('nanoid')
  assert.strictEqual(nanoid(), '1234'); // works as it should
})

How often does it reproduce? Is there a required condition?

  1. For imported packages it seems 100% consistent. It also occurs for a module mock that uses defaultExport.
  2. Node core modules do not reproduce this issue

What is the expected behavior? Why is that the expected behavior?

CJS & MJS files should receive a mocked module

What do you see instead?

CJS files receive the original module, not the mock. MJS files are fine as they receive the mock.

Additional information

No response

@sloops77
Copy link
Author

sloops77 commented May 8, 2025

Seems related #53634

@ljharb
Copy link
Member

ljharb commented May 8, 2025

The "module" field is nonstandard and node doesn't look at it, afaik. Does the same thing happen if you just delete the module field?

@sloops77
Copy link
Author

sloops77 commented May 10, 2025

@ljharb
You are correct - after some experimentation is seems related to the use of exports

The example package should be installed into node_modules.

// index.cjs
const add = (x, y) => x+y;

module.exports = {add}
// index.js
export const add = (x, y) => x+y;
{
  "name": "example-package",
  "type": "module",
  "main": "index.js",
  "exports": {
    "default": {
      "import": "./index.js",
      "require": "./index.cjs"
    }
  },
  "private": true
}
// test.js
mock.module('example-package', {
  namedExports: {
    add(x, y) {
      return 1+x+y;
    }
  },
});

describe('example-package', () => {
  it('mocking problem in cjs', () => {
    const {add} = require('example-package')
    assert.strictEqual(add(4,5), 10); // fails
  })

  it('mocking works for esm', async () => {
    const {add} = await import('example-package');
    assert.strictEqual(add(4,5), 10);
  })
})

Remove exports from package.json and the tests (and mock) works as expected

@sloops77 sloops77 changed the title Test runner module mocking doesn't work for CJS if package exports main and module Test runner module mocking doesn't work for CJS if package exports are specified May 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants