npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mock-require-lazy

v3.0.0

Published

Mock CommonJS require and native ESM import, together or separately, with lazy factories. A drop-in replacement for mock-require

Readme

mock-require-lazy

Replace dependencies loaded through require() or native ESM imports, with immediate values or lazy factories.

About

This is a fork of mock-require that adds lazy loading and keeps its CommonJS API. The import and require package entries share one registry and cleanup API.

Installation

npm install mock-require-lazy

Usage

var mock = require("mock-require-lazy");

mock("http", {
  request: function () {
    console.log("http.request called");
  },
});

var http = require("http");
http.request(); // 'http.request called'

// lazy
mock("path", function () {
  return {
    join: function () {
      console.log("path.join called");
    }
  };
}, true);

var path = require("path");
path.join(); // 'path.join called'

mock.require(path, replacement, { lazy: true }) is the explicit require form; the existing mock(path, factory, true) remains valid. A function without lazy mode is a replacement value, not a factory.

Native ESM

Register mocks before importing the subject. Static imports inside a subsequently loaded subject are intercepted; an already evaluated module's bindings do not change.

import mock from 'mock-require-lazy';

async function test() {
  await mock.import('fs', () => ({ readFileSync: () => 'mocked' }), {
    lazy: true,
    exports: ['readFileSync'],
  });
  try {
    const fs = await import('fs');
    console.log(fs.readFileSync()); // mocked
  } finally {
    mock.stopAll();
  }
}
test();

mock.import(path, namespace) accepts an object containing named exports and, when needed, a default property. Use { default: fn } for a function-valued default. Lazy factories require { lazy: true, exports: ['default', 'name'] } so Node can link export names without running the factory. Factories are synchronous; promise-valued exports inside the returned object are allowed.

await mock.import('./dependency.mjs', './replacement.mjs') redirects to another module and preserves its actual namespace. Both paths resolve from the registration caller. The named exports mockImport, mockRequire, reImport, reRequire, stop, and stopAll expose the same functions as the attached methods.

Mocking require and import together

mock(path, replacement, { mode: 'both' }) registers one replacement for both CommonJS require() and native ESM import(). Unlike the plain synchronous form, this call is asynchronous and returns a promise:

import mock from 'mock-require-lazy';
import Module from 'module';

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

await mock('./fs-wrapper.js', { readFileSync: () => 'mocked' }, { mode: 'both' });
require('./fs-wrapper.js').readFileSync(); // 'mocked'
(await import('./fs-wrapper.js')).readFileSync(); // 'mocked'
mock.stop('./fs-wrapper.js');

replacement is one of:

  • a namespace object, used as-is for require()'s module.exports and as the source of import()'s named exports;
  • a module redirect string, resolved from the registration caller under each loader's own conditions;
  • with lazy: true, a synchronous factory returning a namespace object. exports is required alongside lazy, exactly as for mock.import, so native import can link export names before the factory runs.

A lazy factory evaluates once, shared across whichever loader reaches it first; a later load from the other loader reuses that result. A throw is not cached. The next load, from either loader, retries the factory.

A callable replacement meant to be invoked directly, rather than a namespace, cannot share an ESM namespace, so mode: 'both' rejects it with a TypeError. Mock a callable value with mock.require, and separately with mock.import if the subject is also imported. mock.require never accepts mode and stays synchronous and require-only; mock.import stays import-only.

Runtime and loader setup

CommonJS mocking retains Node 0.8 support, including createRequire() calls made from ESM on newer Node. Native import mocking requires Node 12.22 or newer and Node's loader hooks.

  • Node 12/14/16 and Node 18 before 18.19 need startup registration: node --loader=mock-require-lazy/loader test.mjs.
  • Node 20 before 20.6 also needs startup registration.
  • Node 18.19+, 20.6+, and newer releases register hooks when the import API is first used.
  • Native require(esm) only participates in import mocking when synchronous hooks are available and reliable: Node 22.22.3+ and later applicable branches. On async-hook runtimes, use native import() for ESM subjects. Ordinary CommonJS require mocking is unaffected. Node still rejects synchronously requiring a top-level-await graph.

Older startup loaders emit Node's experimental warning. Mocks are process-global, not isolated between parallel tests; use separate processes or workers with their own setup for independent mock state.

Extensions and TypeScript

The library follows the active resolver. CommonJS keeps its implicit-extension lookup. Native ESM requires explicit relative extensions unless the installed loader supplies a different policy. Alternate requests match when the resolver identifies the same module. Distinct .ts and .js files are not automatically aliases; ESM queries and fragments retain their identities. Bare packages resolve from the caller using the operation's import or require conditions.

Compatibility change: bare CommonJS registrations now resolve from the registration caller, rather than this library's directory. Separate copies of the same package no longer accidentally share a mock. Register each resolved copy when both should be replaced. The callable API and boolean lazy argument remain supported.

The library does not transpile TypeScript. Install a loader such as ts-swc-loaders, or compile first. .cts forces CommonJS and .mts forces ESM; .ts follows the package and loader configuration. Tests exercise typed subjects and real dependencies, not merely mocks registered against typed filenames.

With ts-swc-loaders 2.9.0, load imported CommonJS TypeScript subjects through await mock.reImport('./subject.cts') after registering require('ts-swc-loaders'), or precompile them. This uses the CJS transform before creating the imported namespace. A plain native .cts import can bypass that transform on newer Node. Native type stripping also does not turn import statements into require.

The tested composite loader supports native TypeScript on Node 12.22, 14, and Node 16.12 onward. On Node 16.0–16.11, ts-swc-loaders lacks the legacy ESM hook; compile ESM TypeScript first. CommonJS TypeScript still works there.

For CommonJS TypeScript, register the CJS loader before loading subjects: require('ts-swc-loaders'). For native ESM on chaining-capable Node, launch with ts-swc node test.mjs and use the import API. Before native loader chaining, use a composite loader or precompile; the tested composite example shows how the hooks compose. The fixture's tsconfig defines which source files are transpiled.

Refresh and cleanup

await mock.reImport('./subject.mjs') refreshes only that subject. Cached dependencies retain their identity unless explicitly refreshed or replaced. For a chain A → B → C, changing C and refreshing A does not rebuild cached B; refresh B, then A, if both need updating. Imported CJS subjects also have their own CJS cache entry cleared.

Successful lazy values are reused for their registration. If a factory throws, the error propagates and a later fresh load can retry. Re-registering starts a new lazy lifecycle.

stop(path) and stopAll() synchronously remove registrations for future loads and cancel pending registrations. Loads that already selected a mock finish with that registration. Cleanup does not mutate existing module objects or namespaces. ESM refresh creates new Node module instances; Node does not provide a general ESM-cache purge, so repeated refreshes consume memory for the process lifetime.

Wrappers can supply { parentURL: import.meta.url } or an absolute caller filename to the require, import, stop, and refresh APIs. Without that option, each call resolves relative paths from its own caller. A wrapper passes the same parentURL to later operations that address its registration.

Missing bare names use one global virtual identity, as in the legacy require API. Existing package copies remain distinct when they resolve to different files. Export names must be JavaScript identifiers, including default; string-named exports such as 'a-b' are not supported across the old ESM runtimes.

NODE_PATH is not required. Node's legacy CommonJS search path remains supported through normal resolved-file matching; it is not added to ESM resolution.

API

mock(path, mockExport, lazyOrOptions?)

path: String

The module that you want to mock. This is the same string you would pass in if you wanted to require the module.

This path should be relative to the current file, just as it would be if you were to require the module from the current file. mock-require-lazy also matches this module when another file requires it using a different relative path.

mockExport : object/function

The function or object you want to be returned from require, instead of the path module's exports.

mockExport : string

The module you want to be returned from require, instead of the path module's export. This allows you to replace modules with other modules. For example, if you wanted to replace the fs module with the path module (you probably wouldn't, but if you did):

lazy : boolean

When true, mockExport must be a factory. The factory runs when the mocked module is first required.

Pass { lazy: true } instead of the boolean for the equivalent explicit form. Pass { mode: 'both' } to register the namespace or redirect for both module loaders; that form returns Promise<void> and must be awaited.

mock('fs', 'path');
require('fs') === require('path'); // true

This is useful if you have a mock library that you want to use in multiple places. For example:

test/spy.js:

module.exports = function () {
  return 'this was mocked';
};

test/a_spec.js:

var mock = require('mock-require-lazy');
mock('../some/dependency', './spy');
...

test/b_spec.js:

var mock = require('mock-require-lazy');
mock('../some/other/dependency', './spy');
...

mock.import(path, replacement, options?)

Registers an ESM namespace object or module redirect and returns Promise<void>. With { lazy: true, exports: [...] }, replacement is a synchronous factory and exports declares the names Node must link before evaluating it. See Native ESM for loader requirements.

mock.stop(path, options?)

path: String

The module you that you want to stop mocking. This is the same string you would pass in if you wanted to require the module.

This will only modify variables used after mock.stop is called. For example:

var mock = require('mock-require-lazy');
mock('fs', { mockedFS: true });

var fs1 = require('fs');

mock.stop('fs');

var fs2 = require('fs');

fs1 === fs2; // false

mock.stopAll()

This function can be used to remove all registered mocks without the need to remove them individually using mock.stop().

mock('fs', {});
mock('path', {});

var fs1 = require('fs');
var path1 = require('path');

mock.stopAll();

var fs2 = require('fs');
var path2 = require('path');

fs1 === fs2; // false
path1 === path2; // false

mock.reRequire(path, options?)

path: String

The file whose cache you want to refresh. This is useful if you're trying to mock a dependency for a file that has already been required elsewhere (possibly in another test file). Normally, Node.js will cache this file, so any mocks that you apply afterwards will have no effect. reRequire clears the cache and allows your mock to work.

var fs = require('fs');
var fileToTest = require('./fileToTest');
mock('fs', {}); // fileToTest is still using the unmocked fs module

fileToTest = mock.reRequire('./fileToTest'); // fileToTest is now using your mock

Note that if the file you are testing requires dependencies that in turn require the mock, those dependencies will still have the unmocked version. You may want to reRequire all of your dependencies to ensure that your mock is always being used.

var fs = require('fs');
var otherDep = require('./otherDep'); // requires fs as a dependency
var fileToTest = require('./fileToTest'); // requires fs and otherDep as a dependency
mock('fs', {}); // fileToTest and otherDep are still using the unmocked fs module

otherDep = mock.reRequire('./otherDep'); // do this to make sure fs is being mocked consistently
fileToTest = mock.reRequire('./fileToTest');

mock.reImport(path, options?)

Returns a promise for a fresh instance of one file module or builtin. It does not refresh the dependency graph. Both refresh methods accept { parentURL } when a wrapper must resolve the request on behalf of another module.

Test

npm test
npm run test:engines
npm run test:esm:boundaries