Version
System:
OS: macOS 15.0.1
CPU: (14) arm64 Apple M3 Max
Binaries:
Node: v25.9.0
npmPackages:
@rstest/core: 0.10.6
@rstest/coverage-v8: 0.10.6
Details
When a module is loaded via a dynamic import() whose specifier is not a statically-analyzable string literal (e.g. a local variable), rs.mock() declarations are not applied to that module's imports. The module is evaluated through Node's native ESM loader, bypassing Rstest's mock transform, so it sees the real implementations.
Using a string literal in the otherwise-identical import() works as expected.
This lives in the same area as #1207 / #1210 (relative-path resolution for non-literal dynamic imports), but the symptom here is different: module mocks are silently ignored rather than a path being mis-resolved. It bit us while migrating a suite from Vitest — a test that mocks node:child_process to stub spawn ended up calling the real spawn and actually launching an Electron app, because the launch script was loaded via await import(relativeModulePathVariable).
Minimal reproduction (no external repo — 3 files)
target.mjs
import { hostname } from 'node:os';
export const probe = () => hostname();
index.test.ts
import { expect, it, rs } from '@rstest/core';
rs.mock('node:os', () => ({ hostname: () => 'MOCKED' }));
it('dynamic import with a string LITERAL -> mock IS applied', async () => {
const mod = await import('./target.mjs');
expect(mod.probe()).toBe('MOCKED'); // ✅ passes
});
it('dynamic import with a VARIABLE -> mock is NOT applied', async () => {
const specifier = './target.mjs';
const mod = await import(specifier);
expect(mod.probe()).toBe('MOCKED'); // ❌ fails: receives the real hostname
});
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({ testEnvironment: 'node', include: ['*.test.ts'] });
Expected
Both cases should apply the rs.mock('node:os') mock — Vitest applies the mock regardless of whether the dynamic-import specifier is a literal or a variable.
Actual
- Literal
import('./target.mjs') → probe() returns 'MOCKED' ✅
- Variable
import(specifier) → probe() returns the real hostname ❌
AssertionError: expected 'CF125V70KK' to be 'MOCKED' // Object.is equality
Reproduce link
Inline minimal code above (3 files, no external repo).
Reproduce Steps
- Put the three files above in an empty folder with
@rstest/core@0.10.6 installed.
- Run
npx rstest run.
- The literal-import test passes; the variable-import test fails because
rs.mock was bypassed for the natively-loaded module.
Version
System: OS: macOS 15.0.1 CPU: (14) arm64 Apple M3 Max Binaries: Node: v25.9.0 npmPackages: @rstest/core: 0.10.6 @rstest/coverage-v8: 0.10.6Details
When a module is loaded via a dynamic
import()whose specifier is not a statically-analyzable string literal (e.g. a local variable),rs.mock()declarations are not applied to that module's imports. The module is evaluated through Node's native ESM loader, bypassing Rstest's mock transform, so it sees the real implementations.Using a string literal in the otherwise-identical
import()works as expected.This lives in the same area as #1207 / #1210 (relative-path resolution for non-literal dynamic imports), but the symptom here is different: module mocks are silently ignored rather than a path being mis-resolved. It bit us while migrating a suite from Vitest — a test that mocks
node:child_processto stubspawnended up calling the realspawnand actually launching an Electron app, because the launch script was loaded viaawait import(relativeModulePathVariable).Minimal reproduction (no external repo — 3 files)
target.mjsindex.test.tsrstest.config.tsExpected
Both cases should apply the
rs.mock('node:os')mock — Vitest applies the mock regardless of whether the dynamic-import specifier is a literal or a variable.Actual
import('./target.mjs')→probe()returns'MOCKED'✅import(specifier)→probe()returns the real hostname ❌Reproduce link
Inline minimal code above (3 files, no external repo).
Reproduce Steps
@rstest/core@0.10.6installed.npx rstest run.rs.mockwas bypassed for the natively-loaded module.