X Tutup
Skip to content

Mocking builtins does not affect already-imported ESM namespace objects #62081

@fenying

Description

@fenying

Version

v24.14.0

Platform

Linux xxxxxxx 6.18.13-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 25 Feb 2026 23:12:35 +0000 x86_64 GNU/Linux

Subsystem

No response

What steps will reproduce the bug?

Here is the reproduce code:

// timer-failed.test.mjs

import * as NodeTest from 'node:test';
import * as NodeTimers from 'node:timers/promises';
import * as NodeAssert from 'node:assert';

NodeTest.describe('Test suite for the package', async () => {

    await NodeTest.it('should pass this test', async (ctx) => {

        ctx.mock.timers.enable({ apis: ['Date', 'setTimeout'], now: 12345 });

        const startTime = Date.now();

        let t1 = 0;

        await Promise.all([
            (async () => {
                await NodeTimers.setTimeout(1000, null);
                t1 = Date.now();
            })(),
            (async () => {
                ctx.mock.timers.runAll();
            })()
        ]);

        console.log(startTime);
        console.log(t1);

        NodeAssert.strictEqual(t1 - startTime, 1000);
    });
});

You can see the 2 console.log will print 12345\n12345, which is incorrect.

If you rewrite the file to CJS module, it works perfectly.

// timer.test.cjs
const NodeTest = require('node:test');
const NodeTimers = require('node:timers/promises');
const NodeAssert = require('node:assert');

NodeTest.describe('Test suite for the package', async () => {

    await NodeTest.it('should pass this test', async (ctx) => {

        ctx.mock.timers.enable({ apis: ['Date', 'setTimeout'], now: 12345 });

        const startTime = Date.now();

        let t1 = 0;

        await Promise.all([
            (async () => {
                await NodeTimers.setTimeout(1000, null);
                t1 = Date.now();
            })(),
            (async () => {
                ctx.mock.timers.runAll();
            })()
        ]);

        console.log(startTime);
        console.log(t1);

        NodeAssert.strictEqual(t1 - startTime, 1000);
    });
});

Or just replace the timer code with await new Promise(resolve => setTimeout(resolve, 1000));, it also works well.

How often does it reproduce? Is there a required condition?

Everytime I run this code

What is the expected behavior? Why is that the expected behavior?

The code should print 12345\n13345, and pass the test.

In ESM, the node:timer/promises with ctx.mock.timers.runAll() should work as same as it does in CJS.

What do you see instead?

12345
12345
▶ Test suite for the package
✖ should pass this test (1002.976086ms)
✖ Test suite for the package (1004.29576ms)

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    esmIssues and PRs related to the ECMAScript Modules implementation.test_runnerIssues and PRs related to the test runner subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      X Tutup