(Updated: )
/ #javascript #jest #node 

Mocking/stubbing the current Date in Jest tests

Curious about Advanced Jest Testing Features?

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.

Get "The Jest Handbook" (100 pages)

I want this

There are situations where new Date() or Date.now is used in application code. That code needs to be tested, and it’s always a struggle to remember how to mock/stub or spy on Date.now/new Date with Jest.

This post goes through multiple approaches to mocking, stubbing and spying on the date constructor using Jest.

There’s a full examples repository at github.com/HugoDF/jest-handbook-examples.

Table of Contents

On using Date.now vs new Date()

Date.now() returns the unix time, ie. “the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.” (see Date.now on MDN).

new Date() returns a new Date object, and behaves differently based on the input passed to it. If called with nothing, it returns the current Date.

The following are equivalent, although the values stored in the two variables will be strictly different.

const now = new Date();
const explicitNow = new Date(Date.now());

The values are strictly different because the “now” is calculated at different times, but since the Date constructor (new Date()) supports passing a unix time to it, the two are equivalent.

Using new Date(Date.now()) makes for code that is a lot easier to test. Mocking a function that returns a number (like Date.now) is a lot easier than mocking a constructor.

For example the full code of the jest-mock-now package is the following (see the code at github.com/mattiaerre/jest-mock-now):

const { NOW } = require('./constants');

module.exports = date => {
  const now = date ? date.getTime() : NOW;
  Date.now = jest.spyOn(Date, 'now').mockImplementation(() => now);
  return now;
};

Subscribe to the Enterprise Node.js and JavaScript newsletter for future posts about mocking new Date() and other constructors.

Using jest.setSystemTime

This functionality is only available when using Jest with “modern” timers (ie. not legacy). “modern” timers are opt-in from Jest 26 and the default from Jest 27.

const date = new Date('2023-05-14');
jest.useFakeTimers().setSystemTime(date);
expect(new Date()).toEqual(date);

Full docs: https://jestjs.io/docs/jest-object#jestsetsystemtimenow-number--date

Using native node:test MockTimers

Note: experimental, requires Node 20+ and won’t work in Jest tests, but Node.js now has a native API that mocks the Date.

import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: new Date('2023-05-14') });
// later
mock.timers.setTime(new Date('2023-05-14').valueOf());

I’ve documented it along with other features in: Mocking/stubbing the Date and timers (setTimeout) in Node.js tests with built-in node:test MockTimers

Full docs: https://nodejs.org/api/test.html#dates

Replacing Date.now with a stub

const literallyJustDateNow = () => Date.now();

test('It should call and return Date.now()', () => {
  const realDateNow = Date.now.bind(global.Date);
  const dateNowStub = jest.fn(() => 1530518207007);
  global.Date.now = dateNowStub;

  expect(literallyJustDateNow()).toBe(1530518207007);
  expect(dateNowStub).toHaveBeenCalled();

  global.Date.now = realDateNow;
});

This isn’t really a Jest-specific trick, we’re just accessing Node global object and replace Date.now with a stub. We’re also being good unit-testing citizens and putting the original global.Date.now implementation back 😇.

Spy on Date.now and add a mock implementation

A terser implementation of a similar test would be using jest.spyOn(global.Date, 'now').mockImplementation().

Our mockImplementation will use a hard-coded date initialised using new Date('valid-date-string') and return valueOf(), which corresponds to the unix time of that date.

const getNow = () => new Date(Date.now());

test('It should create correct now Date', () => {
  jest
    .spyOn(global.Date, 'now')
    .mockImplementationOnce(() =>
      new Date('2019-05-14T11:01:58.135Z').valueOf()
    );

  expect(getNow()).toEqual(new Date('2019-05-14T11:01:58.135Z'));
});

This has the advantage of not having to deal with replacing the real date or putting it back.

Mock the whole Date class with a fixed date instance

Credit to Jamie Webb (webb04) who mentions this solution in the “Mocking current time for Date” Jest issue.

const getCurrentDate = () => new Date();
let realDate;

test('It should create new date', () => {
  // Setup
  const currentDate = new Date('2019-05-14T11:01:58.135Z');
  realDate = Date;
  global.Date = class extends Date {
    constructor(date) {
      if (date) {
        return super(date);
      }

      return currentDate;
    }
  };

  expect(getCurrentDate()).toEqual(new Date('2019-05-14T11:01:58.135Z'));

  // Cleanup
  global.Date = realDate;
});

Like we mentioned in the introduction, mocking the whole class is very heavy-handed.

Spy on new Date() constructor and add a mock implementation

Credit to Paul Melero (gangsthub) who mentions this solution in the “Mocking current time for Date” Jest issue.

const getCurrentDate = () => new Date();
test('It should create new date', () => {
  jest
    .spyOn(global, 'Date')
    .mockImplementationOnce(() => new Date('2019-05-14T11:01:58.135Z'));

  expect(getCurrentDate()).toEqual(new Date('2019-05-14T11:01:58.135Z'));
});

This is nice and terse but relies on JavaScript hoisting madness and knowing that Jest allows you to mock global.Date.

Full examples repository at github.com/HugoDF/jest-handbook-examples.

unsplash-logoBryce Barker

Author

Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Curious about Advanced Jest Testing Features?

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library. Get "The Jest Handbook" (100 pages)