2.3 Example: Mocking the global Date object

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 section goes through 5 approaches to mocking, stubbing and spying on the date constructor using Jest. This is a great example of how one might stub out a global object, constructor or method.

Date.now vs new Date(): the importance of types and function signatures

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.

Using jest fake timers and 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.

test('It should create correct now Date', () => {
  const date = new Date('2023-05-14');
  jest.useFakeTimers().setSystemTime(date);
  expect(new Date()).toEqual(date);
});

The test output is as follows, showing that we successfully mocked Date.now:

npx jest src/02.03-date-set-system-time.test.js
 PASS  src/02.03-date-set-system-time.test.js
  ✓ It should create correct now Date (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

This is quite specific to Jest or other environments where we’re provided hooks to mock the date (eg. Node.js now has this functionality built into the node:test package), we’ll now look at some other generalised approaches to mocking and stubbing globals.

Replacing Date.now with a stub

This isn’t really a Jest-specific trick. We’ll access the Node.js global object and replace Date.now with a stub.

To be good unit-testing citizens, we’ll put the original global.Date.now implementation back after the test has run.

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;
});

The test output is as follows, showing that we successfully mocked Date.now:

npx jest src/02.03-date-now-stub.test.js
 PASS  src/02.03-date-now-stub.test.js
  ✓ It should call and return Date.now() (3ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Spy on Date.now and add a mock implementation

A terser implementation of replacing Date.now with a stub would be to use 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.

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

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'));
});

The test output is as follows, the test still passes.

npx jest src/02.03-date-now-spy.test.js
 PASS  src/02.03-date-now-spy.test.js
  ✓ It should create correct now Date (5ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Mock the whole Date class with a fixed date instance

So far we’ve just replaced the .now function and changed its output.

An alternative approach is to override the global.Date. Instead of global.Date pointing to the actual Date, we replace it with our own mock class.

Mocking the whole class is a bit heavy-handed and might create tests that just test nothing.

For example for each Date method we are using in the code, we’ll be tempted to override it with a stub instead of letting it fall through to the extended Date class.

The advantage however is that for this example test, we are being very precise and only overriding behaviour of Date when it’s being initialised without any value being passed to it.

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;
});

This test also passes as per the following test output.

npx jest src/02.03-mock-date-class.test.js
 PASS  src/02.03-mock-date-class.test.js
  ✓ It should create new date (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

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

One fun aspect of constructors in JavaScript is that they’re really “just functions”. Which means we can spy them using jest.spyOn.

That means that we can spy on global.Date and set a mockImplementation, which can be a hardcoded new Date().

This is nice and terse, the code relies on JavaScript hoisting. The hardcoded new Date() is a call to the function that is being spied on, so it seems a bit counterintuitive that this works.

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'));
});

It works as expected, as per test run output.

npx jest src/02.03-spy-date-constructor.test.js
 PASS  src/02.03-spy-date-constructor.test.js
  ✓ It should create new date (5ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Aside: a look at the implementation of jest-mock-now

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;
};

This is quite neat but having an obvious mock being set up and torn down is probably quite useful.

The different permuation on mocking Date.now vs the Date constructor illustrate different techniques that can be applied to mocking global objects and functions in JavaScript with Jest.

The following section will look at how to stub out a parent class when application code inherits from it.

Jump to table of contents