I'm using AVA + sinon to build my unit test. Since I need ES6 modules and I don't like babel, I'm using mjs files all over my project, including the test files. I use "--experimental-modules" argument to start my project and I use "esm" package in the test. The following is my ava config and the test code.
"ava": {
"require": [
"esm"
],
"babel": false,
"extensions": [
"mjs"
]
},
// test.mjs
import test from 'ava';
import sinon from 'sinon';
import { receiver } from '../src/receiver';
import * as factory from '../src/factory';
test('pipeline get called', async t => {
const stub_factory = sinon.stub(factory, 'backbone_factory');
t.pass();
});
But I get the error message:
TypeError {
message: 'ES Modules cannot be stubbed',
}
How can I stub an ES6 module without babel?
2024 edit Check out my (quite extensive) guide on real world dependency stubbing that touches approaches that uses both tooling and manual DI approaching. It should satisfy most questions; showing how to overcome issues with Typescript/transpilation, different runtime issues, etc.
I link to working code (one branch per approach) and you can for instance see an example project that shows how to configure a project using TypeScript, ESM, SWC, Mocha and Sinon. It relies on TestDouble for module replacement. Here is that code: https://github.com/fatso83/sinon-swc-bug/tree/ts-esm/
According to John-David Dalton, the creator of the esm package, it is only possible to mutate the namespaces of *.js
files - *.mjs
files are locked down.
That means Sinon (and all other software) is not able to stub these modules - exactly as the error message points out. There are two ways to fix the issue here:
.js
to make the exports mutable. This is the least invasive, as the mutableNamespace
option is on by default for esm
. This only applies when you use the esm
loader, of course.The tech stack agnostic terminology for option 2 is a link seam - essentially replacing Node's default module loader. Usually one could use TestDouble (which wraps Quibble), ESMock, proxyquire
or rewire
, meaning the test above would look something like this when using Proxyquire:
// assuming that `receiver` uses `factory` internally
// comment out the import - we'll use proxyquire
// import * as factory from '../src/factory';
// import { receiver } from '../src/receiver';
const factory = { backbone_factory: sinon.stub() };
const receiver = proxyquire('../src/receiver', { './factory' : factory });
Modifying the proxyquire example to use TestDouble or ESMock (both supports ESM natively) should be trivial.