Skip to content
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

Feature Request: Module level mocking #51164

Closed
jasonwilliams opened this issue Dec 15, 2023 · 10 comments · Fixed by #52848
Closed

Feature Request: Module level mocking #51164

jasonwilliams opened this issue Dec 15, 2023 · 10 comments · Fixed by #52848
Labels
feature request Issues that request new features to be added to Node.js. test_runner

Comments

@jasonwilliams
Copy link

What is the problem this feature will solve?

Currently you can't mock external or built-in modules which makes it difficult if you code imports a module that may run some side-effects and you have no control over it.

There may be some built in modules which you need to stub out but don't have access to during a test run.

What is the feature you are proposing to solve the problem?

Allowing us to use the built-in test runner to mock modules entirely.

There has already been some work in this by @cjihrig

I think the biggest things missing before I can open a PR are related to ESM loaders and are tracked in #49472 and #49473.

Both of those issues look to be resolved now, I don't know what the current blockers are for this

What alternatives have you considered?

No response

@jasonwilliams jasonwilliams added the feature request Issues that request new features to be added to Node.js. label Dec 15, 2023
@jrson83
Copy link

jrson83 commented Dec 16, 2023

When I was searching for a workaround, how to mock imports with the native test runner I found this looking very similar:

https://github.com/nodejs/node/blob/main/test/es-module/test-esm-loader-mock.mjs

I can't tell if this is already integrated, just missing the new types in @types/node?

@aduh95
Copy link
Contributor

aduh95 commented Dec 24, 2023

It's already possible to mock things as the test linked below using the experimental Customization Hook API (https://nodejs.org/api/module.html#customization-hooks). Note that it should work only on Node.js 18.19+/20.6+.

Here are the files to set it up, and as you can see, it's only using public APIs:

https://github.com/nodejs/node/blob/6a5394ea7dc62b044ed826a7c8d18506ab532b96/test/fixtures/es-module-loaders/mock.mjs
https://github.com/nodejs/node/blob/6a5394ea7dc62b044ed826a7c8d18506ab532b96/test/fixtures/es-module-loaders/mock-loader.mjs

@jrson83
Copy link

jrson83 commented Dec 24, 2023

It's already possible to mock things as the test linked below using the experimental Customization Hook API (https://nodejs.org/api/module.html#customization-hooks)

This information should be added to the test runner docs. I was searching for a working solution to mock modules for days.

@ronjouch
Copy link

It's already possible to mock things as the test linked below using the experimental Customization Hook API. Note that it should work only on Node.js 18.19+/20.6+.

Here are the files to set it up, and as you can see, it's only using public APIs: test/fixtures/es-module-loaders/mock.mjs , test/fixtures/es-module-loaders/mock-loader.mjs

@aduh95 thanks, but these files leave me confused. The path of these things .../test/fixtures/es-module-loaders/... suggests this is ESM setup for Nodejs' own tests, right?

If so, then are we supposed to replicate such a weighty setup in every project where we want to mock module methods? Or do you know if a nicer/lighter way will end up shipping to test_runner? Is there a reason it's not exposed yet, or is the reason merely "because nobody shipped it yet"?

@aduh95
Copy link
Contributor

aduh95 commented Jan 15, 2024

The path of these things .../test/fixtures/es-module-loaders/... suggests this is ESM setup for Nodejs' own tests, right?

Not at all, it's a test that validates mocking of module works (and keeps working). Node.js own tests do not need mocking.

If so, then are we supposed to replicate such a weighty setup in every project where we want to mock module methods? Or do you know if a nicer/lighter way will end up shipping to test_runner? Is there a reason it's not exposed yet, or is the reason merely "because nobody shipped it yet"?

Well nobody shipped it yet indeed, I haven't tried myself so I can't tell you what would be the road blockers. I think it should be feasible to create an npm package that exposes a mock API.

@ronjouch
Copy link

Thx @aduh95, understood.

Note to future self and passerbys: there’s similar discussion and links on stubbing ESM modules at sinonjs/sinon#1832, some of it is probably useful outside of Sinon and in the context of mocking with Node-builtin t.mock.

@hparra
Copy link

hparra commented Apr 3, 2024

From https://cjihrig.com/test_runner_wishlist_2024 (2024-01-09):

I have put together a short wishlist of improvements that I would love to see in Node's test runner even though I have moved on from Node.js myself.

Module mocking is something that I actually already implemented in a git branch. However, mocking ESM, requires using Node's experimental module customization hooks. These experimental APIs were changing pretty drastically while I was working on module mocking, and in my opinion, they are still not adequate for building a stable module mocking API in Node's test runner.

I hope someone is willing to fight the uphill battle of adding these features to core.

Hooks are RC in 21. Not sure how current Colin's opinion is since wip branch is from August 2023.

Thank you Colin and everyone else who led me here. Open source can be hard.

@cjihrig
Copy link
Contributor

cjihrig commented Apr 3, 2024

Just a heads up - I plan to resume working on this. It will need to be an experimental feature though until the underlying modules APIs stabilize.

@himself65
Copy link
Member

I made a simple script to replace a module to fixtures files

- dist
	- index.js
- fixtures
	- index.ts
- tests
	- index.test.ts 
// mock-module.js
import { stat } from "node:fs/promises";
import { join, relative } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const packageDistDir = fileURLToPath(new URL("./dist", import.meta.url));
const fixturesDir = fileURLToPath(new URL("./fixtures", import.meta.url));

export async function resolve(specifier, context, nextResolve) {
  const result = await nextResolve(specifier, context);
  if (result.format === "builtin" || result.url.startsWith("node:")) {
    return result;
  }
  const targetUrl = fileURLToPath(result.url).replace(/\.js$/, ".ts");
  const relativePath = relative(packageDistDir, targetUrl);
  if (relativePath.startsWith(".") || relativePath.startsWith("/")) {
    return result;
  }
  const url = pathToFileURL(join(fixturesDir, relativePath)).toString();
  const exist = await stat(fileURLToPath(url))
    .then((stat) => stat.isFile())
    .catch((err) => {
      if (err.code === "ENOENT") {
        return false;
      }
      throw err;
    });
  if (!exist) {
    return result;
  }
  return {
    url,
    format: "module",
  };
}
// mock-register.js
import { register } from "node:module";

register("./mock-module.js", import.meta.url);
node --import tsx --import ./mock-register.js --test ./tests/index.test.ts

@cjihrig
Copy link
Contributor

cjihrig commented May 5, 2024

#52848

nodejs-github-bot pushed a commit that referenced this issue May 19, 2024
This commit adds experimental module mocking to the test runner.

PR-URL: #52848
Fixes: #51164
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Issues that request new features to be added to Node.js. test_runner
Projects
Status: Pending Triage
Development

Successfully merging a pull request may close this issue.

8 participants