Overview of esmock
mainfetch or Date) to create controlled testing environments.repository·main·Indexed 42 days ago
https://github.com/iambumblehead/esmockA utility for unit testing that provides native ESM (ECMAScript Modules) import and globals mocking. It allows developers to intercept module imports, package dependencies, and global variables like fetch or Date to create controlled testing environments. Features include strict and strictest modes for non-merging mocks, support for dynamic await import() via esmock.p, and cache management using esmock.purge. Version 2.7.6.
fetch or Date) to create controlled testing environments.The third argument to esmock allows you to mock objects that are used throughout the entire import tree. This is useful for mocking built-in modules like fs or global functions like fetch.
import test from 'node:test'
import assert from 'node:assert'
import esmock from 'esmock'
test('full import tree mocks —third param', async () => {
const { getFile } = await esmock('../src/main.js', {}, {
// mocks *every* fs.readFileSync inside the import tree
fs: { readFileSync: () => 'returned to 🌲 every caller in the tree' }
})
assert.equal(getFile(), 'returned to 🌲 every caller in the tree')
})
test('mock fetch, Date, setTimeout and any globals', async () => {
const { userCount } = await esmock('../Users.js', {
'../req.js': await esmock('../req.js', {
import: { // define globals like 'fetch' on the import namespace
fetch: async () => ({
status: 200,
json: async () => [['jim','😄'],['jen','😊']]
})
}
})
})
assert.equal(await userCount(), 2)
})You can mock specific dependencies by providing their name or path in the second argument of esmock. This includes standard npm packages, subpath imports (aliases like #icon), and relative file paths.
import test from 'node:test'
import assert from 'node:assert'
import esmock from 'esmock'
test('package, alias and local file mocks', async () => {
const cookup = await esmock('../src/cookup.js', {
addpkg: (a, b) => a + b,
'#icon': { coffee: '☕', bacon: '🥓' },
'../src/breakfast.js': {
default: () => ['coffee', 'bacon'],
addSalt: meal => meal + '🧂'
}
})
assert.equal(cookup('breakfast'), '☕🥓🧂')
})When your code uses dynamic await import(), use esmock.p. This version of the function ensures that mock definitions are kept in a cache, so when the dynamic import is called, the mocked definition is returned correctly.
import test from 'node:test'
import assert from 'node:assert'
import esmock from 'esmock'
test('mocks "await import()" using esmock.p', async () => {
const doAwaitImport = await esmock.p('../awaitImportLint.js', {
eslint: { ESLint: cfg => cfg }
})
assert.equal(await doAwaitImport('cfg🛠️'), 'cfg🛠️')
})The primary esmock function accepts three arguments:
path: The path to the target module being tested.childmocks: An object defining mocks for modules imported by the target module (supports package names, aliases, and local file paths).globalmocks: An object defining mocks that are applied to the global scope or the import namespace, affecting the entire import tree.Note: For Node.js versions prior to v20.6.0, you must use the --loader command line argument with esmock. Current versions of Node.js do not require this.
await esmock(
'./to/module.js', // path to target module being tested
{ ...childmocks }, // mock definitions imported by target module
{ ...globalmocks }) // mock definitions imported everywhereBy default, esmock may merge mocks with existing module definitions. If you want to completely replace the original module definitions without merging them, use esmock.strict. If your mock object is missing properties that the original module had, accessing those properties will result in a TypeError.
import test from 'node:test'
import assert from 'node:assert'
import esmock from 'esmock'
test('esmock.strict mocks', async () => {
// replace original module definitions and do not merge them
const pathWrapper = await esmock.strict('../src/pathWrapper.js', {
path: { dirname: () => '/path/to/file' }
})
// error, because "path" mock above does not define path.basename
await assert.rejects(() => pathWrapper.basename('/dog.🐶.png'), {
name: 'TypeError',
message: 'path.basename is not a function'
})
})The esmock repository uses a flat configuration for ESLint. It integrates @eslint/js, @eslint/markdown, and typescript-eslint to enforce code quality across JavaScript, TypeScript, and Markdown files. Key features include:
@eslint/markdown to process code blocks within .md files.typescript-eslint recommended rules with specific overrides to allow any types and unused expressions/variables in certain contexts.describe, it, test, expect) and specific esmock utilities (esmock, childmocks, globalmocks) within Markdown code blocks.When using esmock, modules are cached to optimize performance. If you need to re-mock a module or ensure a fresh import, you can manually purge it using the purge function.
To use purge, you must pass the imported module object returned by esmock. The function checks if the object is a valid mockable entity (object or function) and contains the required esmkTreeId.
import esmock, { purge } from 'esmock';
const mod = await esmock('./module.js', import.meta.url, { './dep.js': {} });
// ... perform tests ...
// Clear the module from the esmock cache
purge(mod);The esmock.strict variant (and the standalone strict export) replaces original module definitions with your provided mock definitions instead of merging them. This is useful when you want to ensure the original implementation is not executed at all.
import { strict } from 'esmock';
const mockedModule = await strict('./my-module.js', import.meta.url, {
'./dependency.js': { mockedValue: true }
});The esmock package exports specialized versions of the main function to control strictness levels. These are useful when you need more rigorous enforcement of module boundaries or dependency resolution.
esmock: The standard version with default strictness.strict: An alias for esmock with strict: 1 enabled.strictest: An alias for esmock with strict: 3 enabled.Each of these also provides a .p() method, which is the same function but with purge: false applied, preventing the automatic purging of the module from the internal cache.
import { strict, strictest } from 'esmock';
// Uses strict mode (level 1)
const mod = await strict('./module.js', import.meta.url, { mock: {} });
// Uses highest strictness (level 3)
const modStrict = await strictest('./module.js', import.meta.url, { mock: {} });
// Use .p() to prevent automatic purging of the module cache
const modNoPurge = await strict.p('./module.js', import.meta.url, { mock: {} });The esmock function is the primary entrypoint for mocking ECMAScript Modules (ESM) and global definitions. It allows you to import a module while providing custom definitions (defs) for its dependencies and custom global definitions (gdefs).
By default, esmock also manages a cache (purging) to ensure that subsequent calls to the same module reflect new mocks. You can control this behavior using the purge option.
Usage Pattern:
await esmock(moduleId, parent, defs, gdefs, options)
moduleId: The string path or URL of the module to import.parent: The module or context requesting the import.defs: An object containing the mocks for specific module imports.gdefs: An object containing mocks for global variables.options: Configuration object (e.g., { purge: false }).import esmock from 'esmock';
const myMock = { method: () => 'mocked' };
const module = await esmock('./target-module.js', import.meta.url, { './dependency.js': myMock });The esmock function allows you to mock imports for a specific module. You provide a modulePath (the module to be mocked), a parent (a URL used to resolve relative specifiers, typically import.meta.url), and defs (a mapping of import specifiers to mock definitions).
By default, esmock merges your mock definitions with the original module definitions. To completely replace the original definitions, use the strict or strictest variants.
import esmock from 'esmock';
// Basic usage: mocking a module
const mockedModule = await esmock('./my-module.js', import.meta.url, {
'./dependency.js': { mockedValue: true }
});