From b5ee33c823289ce3d39d7e060f5c886f7a2fffaf Mon Sep 17 00:00:00 2001 From: Siddhartha Singh Date: Sat, 7 Mar 2026 08:20:39 +0530 Subject: [PATCH] test_runner: sync ESM exports when mocking timers This change ensures that ESM imports of node:timers/promises are correctly mocked when using mock.timers.enable(). This is achieved by calling syncBuiltinESMExports() whenever the mocked timers are toggled or reset. Fixes: https://github.com/nodejs/node/issues/62081 --- lib/internal/test_runner/mock/mock_timers.js | 9 ++-- test/parallel/test-runner-mock-timers-esm.mjs | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-runner-mock-timers-esm.mjs diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index 643128c3f0031f..666998c023490b 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -24,6 +24,8 @@ const { validateNumber, validateStringArray, } = require('internal/validators'); +const { Module } = require('internal/modules/cjs/loader'); +const { syncBuiltinESMExports } = Module; const { AbortError, @@ -440,9 +442,9 @@ class MockTimers { const eventIt = EventEmitter.on(emitter, 'data'); const timer = this.#createTimer(true, - () => emitter.emit('data'), - interval, - options); + () => emitter.emit('data'), + interval, + options); try { // eslint-disable-next-line no-unused-vars @@ -628,6 +630,7 @@ class MockTimers { const target = activate ? options.toFake : options.toReal; ArrayPrototypeForEach(this.#timersInContext, (timer) => target[timer]()); this.#isEnabled = activate; + syncBuiltinESMExports(); } /** diff --git a/test/parallel/test-runner-mock-timers-esm.mjs b/test/parallel/test-runner-mock-timers-esm.mjs new file mode 100644 index 00000000000000..2eb1955ead5da7 --- /dev/null +++ b/test/parallel/test-runner-mock-timers-esm.mjs @@ -0,0 +1,44 @@ +import { describe, it } from 'node:test'; +import { setTimeout } from 'node:timers/promises'; +import assert from 'node:assert'; + +describe('Mock Timers ESM Regression', () => { + it('should work with node:timers/promises and runAll() in ESM', async (t) => { + t.mock.timers.enable({ apis: ['Date', 'setTimeout'] }); + const startTime = Date.now(); + let t1 = 0; + + await Promise.all([ + (async () => { + await setTimeout(1000); + t1 = Date.now(); + })(), + (async () => { + // Wait for the next tick to ensure setTimeout has been called + await new Promise(resolve => process.nextTick(resolve)); + t.mock.timers.runAll(); + })() + ]); + + assert.strictEqual(t1 - startTime, 1000); + }); + + it('should work with node:timers/promises and tick() in ESM', async (t) => { + t.mock.timers.enable({ apis: ['Date', 'setTimeout'] }); + const startTime = Date.now(); + let t1 = 0; + + await Promise.all([ + (async () => { + await setTimeout(500); + t1 = Date.now(); + })(), + (async () => { + await new Promise(resolve => process.nextTick(resolve)); + t.mock.timers.tick(500); + })() + ]); + + assert.strictEqual(t1 - startTime, 500); + }); +});