Skip to content

Commit 379b141

Browse files
committed
feat: add support for ESM default export config and corresponding tests
1 parent 08df5d8 commit 379b141

File tree

3 files changed

+140
-28
lines changed

3 files changed

+140
-28
lines changed

readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ module.exports = {
185185
};
186186
```
187187

188+
## Node 22.12+ and ESM configs
189+
190+
Node 22.12 turned on `require()` support for ESM by default. If your config is an `.mjs` file with a default export, `@shelf/jest-mongodb` now imports it automatically (and still supports CommonJS configs). If you prefer the old behavior, you can run Jest with `--no-experimental-require-module`.
191+
188192
## See Also
189193

190194
- [jest-dynamodb](https://github.com/shelfio/jest-dynamodb)

src/helpers.ts

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,114 @@
1+
import {spawnSync} from 'child_process';
12
import {resolve} from 'path';
23
import type {MongoMemoryReplSet, MongoMemoryServer} from 'mongodb-memory-server';
34
import type {Config} from './types';
45

5-
const configFile = process.env.MONGO_MEMORY_SERVER_FILE || 'jest-mongodb-config.js';
6-
76
type MongoMemoryReplSetOpts = NonNullable<ConstructorParameters<typeof MongoMemoryReplSet>[0]>;
87
type MongoMemoryServerOpts = NonNullable<ConstructorParameters<typeof MongoMemoryServer>[0]>;
98

109
export function isMongoMemoryReplSetOptions(
1110
options?: MongoMemoryReplSetOpts | MongoMemoryServerOpts
1211
): options is MongoMemoryReplSetOpts {
13-
return Boolean((options as MongoMemoryReplSetOpts).replSet);
12+
return Boolean((options as MongoMemoryReplSetOpts | undefined)?.replSet);
1413
}
1514

16-
export function getMongodbMemoryOptions(
17-
cwd: string
18-
): MongoMemoryReplSetOpts | MongoMemoryServerOpts | undefined {
15+
function getConfigFile() {
16+
return process.env.MONGO_MEMORY_SERVER_FILE || 'jest-mongodb-config.js';
17+
}
18+
19+
const configCache = new Map<string, Config | null>();
20+
21+
function importConfig(configPath: string): Config | undefined {
1922
try {
20-
// eslint-disable-next-line @typescript-eslint/no-require-imports
21-
const {mongodbMemoryServerOptions}: Config = require(resolve(cwd, configFile));
23+
// Node 22.12+ can let `require` load ESM by default. When Jest runs in CJS mode
24+
// and the config is `.mjs`, spawn a one-off Node process to import it as ESM
25+
// and return the plain JSON payload.
26+
const {status, stdout} = spawnSync(
27+
process.execPath,
28+
[
29+
'--input-type=module',
30+
'--eval',
31+
[
32+
'import {pathToFileURL} from "node:url";',
33+
`const mod = await import(pathToFileURL(${JSON.stringify(configPath)}).href);`,
34+
'const payload = mod.default ?? mod;',
35+
'console.log(JSON.stringify(payload));',
36+
].join('\n'),
37+
],
38+
{encoding: 'utf8'}
39+
);
2240

23-
return mongodbMemoryServerOptions;
41+
if (status === 0 && stdout.trim()) {
42+
return JSON.parse(stdout) as Config;
43+
}
2444
} catch {
25-
return {
26-
binary: {
27-
checkMD5: false,
28-
},
29-
instance: {},
30-
};
45+
// ignore and fall through to undefined
3146
}
47+
48+
return undefined;
3249
}
3350

34-
export function getMongoURLEnvName(cwd: string) {
35-
try {
36-
// eslint-disable-next-line @typescript-eslint/no-require-imports
37-
const {mongoURLEnvName}: Config = require(resolve(cwd, configFile));
51+
function loadConfig(cwd?: string): Config | undefined {
52+
const baseDir = cwd || process.cwd();
3853

39-
return mongoURLEnvName || 'MONGO_URL';
40-
} catch {
41-
return 'MONGO_URL';
54+
if (configCache.has(baseDir)) {
55+
return configCache.get(baseDir) ?? undefined;
4256
}
43-
}
4457

45-
export function shouldUseSharedDBForAllJestWorkers(cwd: string) {
58+
const configPath = resolve(baseDir, getConfigFile());
59+
4660
try {
4761
// eslint-disable-next-line @typescript-eslint/no-require-imports
48-
const {useSharedDBForAllJestWorkers}: Config = require(resolve(cwd, configFile));
62+
const loadedConfig = require(configPath) as Config | {default?: Config};
4963

50-
if (typeof useSharedDBForAllJestWorkers === 'undefined') {
51-
return true;
64+
if (loadedConfig && typeof (loadedConfig as {default?: Config}).default !== 'undefined') {
65+
const config = (loadedConfig as {default?: Config}).default;
66+
configCache.set(baseDir, config ?? null);
67+
68+
return config;
5269
}
5370

54-
return useSharedDBForAllJestWorkers;
71+
const config = loadedConfig as Config;
72+
configCache.set(baseDir, config ?? null);
73+
74+
return config;
5575
} catch {
76+
const importedConfig = importConfig(configPath);
77+
configCache.set(baseDir, importedConfig ?? null);
78+
79+
return importedConfig;
80+
}
81+
}
82+
83+
export function getMongodbMemoryOptions(
84+
cwd?: string
85+
): MongoMemoryReplSetOpts | MongoMemoryServerOpts | undefined {
86+
const config = loadConfig(cwd);
87+
88+
if (config?.mongodbMemoryServerOptions) {
89+
return config.mongodbMemoryServerOptions;
90+
}
91+
92+
return {
93+
binary: {
94+
checkMD5: false,
95+
},
96+
instance: {},
97+
};
98+
}
99+
100+
export function getMongoURLEnvName(cwd?: string) {
101+
const config = loadConfig(cwd);
102+
103+
return config?.mongoURLEnvName || 'MONGO_URL';
104+
}
105+
106+
export function shouldUseSharedDBForAllJestWorkers(cwd?: string) {
107+
const config = loadConfig(cwd);
108+
109+
if (typeof config?.useSharedDBForAllJestWorkers === 'undefined') {
56110
return true;
57111
}
112+
113+
return config.useSharedDBForAllJestWorkers;
58114
}

test/helpers-esm-config.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {mkdtempSync, writeFileSync} from 'fs';
2+
import {tmpdir} from 'os';
3+
import {join} from 'path';
4+
import {
5+
getMongoURLEnvName,
6+
getMongodbMemoryOptions,
7+
isMongoMemoryReplSetOptions,
8+
shouldUseSharedDBForAllJestWorkers,
9+
} from '../src/helpers';
10+
11+
describe('helpers with ESM default export config', () => {
12+
const originalConfigFile = process.env.MONGO_MEMORY_SERVER_FILE;
13+
const tempDir = mkdtempSync(join(tmpdir(), 'jest-mongodb-esm-config-'));
14+
const configPath = join(tempDir, 'jest-mongodb-config.mjs');
15+
16+
beforeAll(() => {
17+
writeFileSync(
18+
configPath,
19+
[
20+
'export default {',
21+
' mongodbMemoryServerOptions: {replSet: {count: 1}},',
22+
" mongoURLEnvName: 'CUSTOM_URL',",
23+
' useSharedDBForAllJestWorkers: false,',
24+
'};',
25+
'',
26+
].join('\n')
27+
);
28+
29+
process.env.MONGO_MEMORY_SERVER_FILE = configPath;
30+
});
31+
32+
afterAll(() => {
33+
if (typeof originalConfigFile === 'undefined') {
34+
delete process.env.MONGO_MEMORY_SERVER_FILE;
35+
} else {
36+
process.env.MONGO_MEMORY_SERVER_FILE = originalConfigFile;
37+
}
38+
});
39+
40+
it('loads options from default export without throwing', () => {
41+
const options = getMongodbMemoryOptions(tempDir);
42+
43+
expect(options).toEqual(expect.objectContaining({replSet: {count: 1}}));
44+
expect(getMongoURLEnvName(tempDir)).toBe('CUSTOM_URL');
45+
expect(shouldUseSharedDBForAllJestWorkers(tempDir)).toBe(false);
46+
expect(isMongoMemoryReplSetOptions(options)).toBe(true);
47+
});
48+
49+
it('treats missing options as non-replSet', () => {
50+
expect(isMongoMemoryReplSetOptions(undefined)).toBe(false);
51+
});
52+
});

0 commit comments

Comments
 (0)