Skip to content

[Bug]: rs.mock is bypassed for modules loaded via dynamic import() with a non-literal specifier #1454

Description

@fi3ework

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

  1. Put the three files above in an empty folder with @rstest/core@0.10.6 installed.
  2. Run npx rstest run.
  3. The literal-import test passes; the variable-import test fails because rs.mock was bypassed for the natively-loaded module.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions