test_runner: Add Date to the supported mock APIs by khaosdoctor · Pull Request #48638 · nodejs/node

This builds on top of @ErickWendel's #47775, I saw the next steps would be to implement the mock timers for Date.now (and thus, the Date object) and performance.now.

This PR implements the Date.now mock, I'll also work on performance.now on another PR to make it simpler to review. This one includes the Docs already updated and the added tests.

This heavily builds on Sinon's Fake Timers for the base edge cases

To-Do

  • Refactor the code to make setTime actually call the tick method and pass the time
  • Make the initial time be 0 or accept an instance of Date, or a specific number to make it's implementation be the same as fake-timers
  • Mock the Date object completely
  • Allow enable to accept multiple overloads

Next iterations

  • Mock performance.now
  • Mock process.hrtime

New MockTimers API

// All that was before, with no changes
MockTimers.reset()  // clean up to the original state
MockTimers.tick(100) // advance in time by 100ms
MockTimers.runAll() // release all timers

// Implementation changes
MockTimers.enable({ timersToEnable: ['setInterval', 'setTimeout', 'Date' ], now: 1000 }) // enable fake timers with the initial timer set (optional)

// New methods
MockTimers.setTime(100) // sets Date.now to the desired value

It's also possible to omit the initial parameter and pass on only the initial epoch, which will enable all timers with that epoch set:

// All that was before, with no changes
MockTimers.reset()  // clean up to the original state
MockTimers.tick(100) // advance in time by 100ms
MockTimers.runAll() // release all timers

// Implementation changes
MockTimers.enable({ now: 1000 }) // enable fake timers with the initial timer set (optional)

// New methods
MockTimers.setTime(100) // sets Date.now to the desired value

Lastly, you can omit all parameters to enable all timers at the epoch 0:

// All that was before, with no changes
MockTimers.reset()  // clean up to the original state
MockTimers.tick(100) // advance in time by 100ms
MockTimers.runAll() // release all timers

// Implementation changes
MockTimers.enable()

// New methods
MockTimers.setTime(100) // sets Date.now to the desired value

Example usage

import assert from 'node:assert';
import { test } from 'node:test';

test('mocks Date.now to whatever value the user sets', (context) => {
  const now = Date.now()
  console.log(now) // not mocked, will print correct timestamp

  context.mock.timers.enable({ apis: ['Date'] });

  // This will be roughly true. Just to take it as example
  assert.strictEqual(now, Date.now()) // if not set, will take the value of Date.now() as initial value
  context.mock.timers.setTime(1000)
  assert.strictEqual(Date.now(), 1000) // Date.now is 1000
});
import assert from 'node:assert';
import { test } from 'node:test';

test('mocks Date.now to whatever value the user sets', (context) => {
  const now = Date.now()
  console.log(now) // not mocked, will print correct timestamp

  context.mock.timers.enable({ apis: ['Date'], now: 1000 });

  // This will be roughly true. Just to take it as example
  assert.strictEqual(Date.now(), 1000) // Date.now is 1000 due to being set in enable()
});

All the remaining APIs are the same