Skip to content

Instantly share code, notes, and snippets.

@booya2nd
Last active September 28, 2023 12:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save booya2nd/dcaa1775fd4c06cd79610e3feea6362c to your computer and use it in GitHub Desktop.
Save booya2nd/dcaa1775fd4c06cd79610e3feea6362c to your computer and use it in GitHub Desktop.
mockESModule workaround for JEST 29.x
import * as path from 'path';
import * as url from 'url';
function forEachDeep(obj, cb, options = { depth: 6 }) {
(function walk(value, property = undefined, parent = null, objPath = []) {
return value && typeof value === 'object' && objPath.length <= options.depth
? Object.entries(value).forEach(([key, val]) =>
walk(val, key, value, [...objPath, key])
)
: cb([property, value], parent, objPath);
})(obj);
}
const NOOP = x => x;
export default async function mockESModule(
moduleSpecifier,
importMeta,
factory = NOOP
) {
let modulePath = moduleSpecifier;
if (moduleSpecifier.startsWith('.')) {
const metaPath = url.fileURLToPath(new URL('./', importMeta.url));
modulePath = path.join(metaPath, moduleSpecifier);
}
const { jest } = importMeta;
const module = await import(modulePath);
const moduleCopy = { ...module };
forEachDeep(moduleCopy, ([prop, value], obj) => {
if (typeof value === 'function') {
obj[prop] = jest.fn(value);
// re-adding stinky dynamic custom properties which jest.fn() may has removed
Object.assign(obj[prop], value);
}
});
const moduleMock = factory(moduleCopy);
jest.unstable_mockModule(moduleSpecifier, () => moduleMock);
return moduleMock;
}
import someMagic from './some-magic.js';
export default function(){
return someMagic() ? 'magic' : 'no magic';
}
import { describe, it, jest } from '@jest/globals';
import mockESModule from './mock-esmodule.js';
// this will mock all functions exported from some-magic.js
const mockMagic = (await mockESModule('./some-magic.js', import.meta)).default;
// use import() in order to apply mocked version of dependencies
const myModule = (await import('./my-module.js')).default;
describe('modulename', () => {
it('invokes the thing', () => {
const result = myModule();
expect(result).toBe('magic');
});
it('throws if magic is falsy', () => {
mockMagic.mockImplementationOnce(() => false);
expect(myModule).toBe('no magic');
expect(mockMagic).toHaveBeenCalled();
})
})
export default function someMagic(){ return true }
@booya2nd
Copy link
Author

booya2nd commented Sep 13, 2022

Key components:
Mock via jest.unstable_mockModule() before import() - you have to use dynamic import() over static import
https://github.com/facebook/jest/blob/main/docs/ECMAScriptModules.md#module-mocking-in-esm

jest.unstable_mockModule('your-dependency', () => ({ default: jest.fn(() => true) }));
const moduleToBeTested = (await import('my-module-to-be-tested.js')).default;

@krinoid
Copy link

krinoid commented Sep 27, 2023

Thanks for sharing, @booya2nd, this is very helpful! Would you be able to specify the license of this code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment