I'm writing a React library MyLibrary
and bundling it with Rollup.js 2.58.3
. I use jest
for unit testing.
Quick summary of the issue
I am unable to mock a module from my library using jest
. This is due to the way rollup "compiles" my code.
Rollup uses its code splitting functionality to create many chunks. In one example, rollup splits Alpha.js
into two chunks: Alpha.js
and Alpha-xxxxxx.js
. Some functionality from the original file is extracted to this "intermediate" chunk (Alpha-xxxxxx.js
).
In my unit test, when mocking any method that was moved into the intermediate file Alpha-xxxxxx.js
, jest
seems to actually load the module from this "intermediate" module rather than the top level module.
This causes the test to fail.
e.g. jest.mock('MyLibrary/dist/Alpha')
does not work since the modules are actually being loaded from Alpha-xxxxxx.js
instead of Alpha.js
Detailed explanation with example
I have a two modules Alpha.js
and Beta.js
in MyLibrary
.
MyLibrary/Alpha.js
export const Aaa = () => {
...
}
...
MyLibrary/Beta.js
import { Aaa } from './Alpha';
...
export const Bbb = () => {
...
}
...
Compiled output generated by rollup
When bundled, rollup.js splits Alpha.js
into 2 chunks Alpha.js
and Alpha-xxxxxx.js
. As a result of the code splitting, the compiled version of Beta.js
now looks something like this:
MyLibrary/dist/Beta.js
import { Aaa } from './Alpha-xxxxxx';
...
export const Bbb = () => {
...
}
...
This compiled module from MyLibrary
is imported in MyApp
and the app seems to import it correctly and works fine.
MyApp/index.js
import { Bbb } from 'MyLibrary/dist/Beta'
...
The problem
The jest
test below fails since it does not correctly mock the Aaa
export.
MyApp/index.test.js
import { Bbb } from 'MyLibrary/dist/Beta';
jest.mock('MyLibrary/dist/Alpha', () => ({
Aaa: jest.fn(),
}));
However, if I mock the intermediate chunk generated by rollup, it works.
import { stuff } from 'MyLibrary/dist/Beta';
jest.mock('MyLibrary/dist/Alpha-xxxxxx', () => ({
Aaa: jest.fn(),
}));
How can I properly mock Alpha
from my library in my jest test?
CodePudding user response:
It sounds like you need to mock a module whose filename base is deterministic, but includes a hash suffix which changes with each build. You haven't provided the full pattern for the filename of the output module chunk, so I'll use a hypothetical example:
Let's say the chunk module that you need to find is emitted in this pattern:
Alpha-b38ca4f6.js
meaning it's the literal Alpha-
followed an alphanumeric hash of length 8, then the extension .js
You can express that with this regular expression:
^Alpha-[a-z0-9]{8}.js$
You can use a helper function to find the correct file name before passing it to Jest. The function should accept a directory and a regular expression for matching the file pattern. Here's an example:
MyLibrary/utils.mjs
:
import path from 'path';
import {promises as fs} from 'fs';
export async function findFileByPattern (dir, regex) {
const fileNames = (await fs.readdir(dir, {withFileTypes: true}))
.filter(entry => entry.isFile())
.map(entry => entry.name);
for (const fileName of fileNames) {
if (!regex.test(fileName)) continue;
const {name} = path.parse(fileName);
return path.join(dir, name);
}
throw new Error('File not found matching the provided pattern');
}
MyApp/index.test.js
:
import { Bbb } from 'MyLibrary/dist/Beta';
import { findFileByPattern } from './utils';
const chunkFileName = await findFileByPattern('MyLibrary/dist', /^Alpha-[a-z0-9]{8}.js$/);
jest.mock(chunkFileName, () => ({
Aaa: jest.fn(),
}));
If desired, you can even use the function to locate the chunk file name in a post-build step, and emit the value as a JSON artifact: then import the JSON in your test and use the file name value. This would allow you to skip the step of enumerating the dist
directory each time the test is run.