137

I have a method which depends on new Date to create a date object and then manipulates it. I'm testing that the manipulation works as expected, so I need to compare the returned date with expected date. In order to do that I need to make sure that new Date returns the same value in the test and in the method being tested. How can I do that?

Is there a way to actually mock the return value of a constructor function?

I could create a module that can be required with a function that provides a date object and can be mocked. But that seems like an unnecessary abstraction in my code.

an example function to be tested...

module.exports = {
  sameTimeTomorrow: function(){
    var dt = new Date();
        dt.setDate(dt + 1);
    return dt;
  }
};

how do I mock the return value of new Date()?

wpp
  • 7,093
  • 4
  • 33
  • 65
Seth Feldkamp
  • 1,406
  • 2
  • 9
  • 8
  • I'm a little unsure here. Could you post code? My guess would be that you should pass a `definedDate` to both functions and when you assign the date you should do `var assignedDate = definedDate || new Date();` to check whether you passed it a testing value or not. Then you can enable/disable by passing/not passing it. – somethinghere Feb 13 '15 at 17:01
  • updated with a (contrived) example. I could indeed add an optional date argument and pass that in for my testing. However, that is not as nice of an api and also my real use case already has 3 arguments to it. So I'm not keen to add another, especially since it could considerably muddy the intent of the method. – Seth Feldkamp Feb 13 '15 at 19:04

15 Answers15

186

Since jest 26, you can use the 'modern' fakeTimers implementation (see article here) wich supports the method jest.setSystemTime.

beforeAll(() => {
    jest.useFakeTimers('modern');
    jest.setSystemTime(new Date(2020, 3, 1));
});

afterAll(() => {
    jest.useRealTimers();
});

Note that 'modern' will be the default implementation from jest version 27.

See documentation for setSystemTime here.

ThdK
  • 9,916
  • 23
  • 74
  • 101
121

Update: this answer is the approach for jest < version 26 see this answer for recent jest versions.


You can mock a constructor like new Date() using jest.spyOn as below:

test('mocks a constructor like new Date()', () => {
  console.log('Normal:   ', new Date().getTime())

  const mockDate = new Date(1466424490000)
  const spy = jest
    .spyOn(global, 'Date')
    .mockImplementation(() => mockDate)

  console.log('Mocked:   ', new Date().getTime())
  spy.mockRestore()

  console.log('Restored: ', new Date().getTime())
})

And the output looks like:

Normal:    1566424897579
Mocked:    1466424490000
Restored:  1566424897608

See the reference project on GitHub.

Note: If you are using TypeScript and you would encounter a compilation error, Argument of type '() => Date' is not assignable to parameter of type '() => string'. Type 'Date' is not assignable to type 'string'. In this case, a workaround is to use the mockdate library, which can be used to change when "now" is. See this question for more details.

Liam
  • 27,717
  • 28
  • 128
  • 190
Yuci
  • 27,235
  • 10
  • 114
  • 113
  • 24
    I get `Argument of type '() => Date' is not assignable to parameter of type '() => string'. Type 'Date' is not assignable to type 'string'.` – Ella Sharakanski Mar 29 '20 at 08:02
  • @EllaSharakanski See my reference project: https://github.com/yucigou/stackoverflow-ref-projects/tree/master/node-app-date-mocking – Yuci Mar 29 '20 at 10:53
  • @Yuci Thanks but I do the exact same thing as you and get an error. Might be related to TypeScript then. I asked my question in a separate post: https://stackoverflow.com/questions/60912023/jest-typescript-mock-date-constructor – Ella Sharakanski Mar 29 '20 at 12:19
  • 1
    @EllaSharakanski See my reply to your question: https://stackoverflow.com/questions/60912023/jest-typescript-mock-date-constructor/60918716#60918716 – Yuci Mar 29 '20 at 18:14
  • 23
    For TS, you can fool Jest and TS: .`mockImplementation(() => mockDate as unknown as string);`. Test like `expect(new Date()).toBe(mockDate);` works… – PhiLho Jun 15 '20 at 08:12
  • Why does the solution not work when mockImplementationOnce is used instead of mockImplementation? – Daniel Huang Jan 16 '21 at 04:41
  • @ThdK Thank you for linking to the anwer for newer jest versions! – Sebastian Rittau Apr 27 '21 at 10:45
27

You can use jasmine's spyOn (jest is built on jasmine) to mock Date's prototype for getDate as follows:

spyOn(Date.prototype, 'setDate').and.returnValue(DATE_TO_TEST_WITH);

SpyOn will also clean up after it's self and only lasts for the scope of the test.

linuxdan
  • 4,476
  • 4
  • 30
  • 41
17

Although the other answers solve the problem, I find it more natural and generally applicable to mock only the Date's "parameterless constructor" behavior while keeping other features of Date intact. For instance, when ISO Date string is passed to the constructor, it is probably reasonable to expect that this specific date is returned as opposed to the mocked Date.

test('spies new Date(...params) constructor returning a mock when no args are passed but delegating to real constructor otherwise', () => {
    const DateReal = global.Date;
    const mockDate = new Date("2020-11-01T00:00:00.000Z");

    const spy = jest
        .spyOn(global, 'Date')
        .mockImplementation((...args) => {
            if (args.length) {
                return new DateReal(...args);
            }
            return mockDate;
        })
        
    const dateNow = new Date();

    //no parameter => mocked current Date returned
    console.log(dateNow.toISOString()); //outputs: "2020-11-01T00:00:00.000Z"

    //explicit parameters passed => delegated to the real constructor
    console.log(new Date("2020-11-30").toISOString()); //outputs: "2020-11-30T00:00:00.000Z"
    
    //(the mocked) current Date + 1 month => delegated to the real constructor
    let dateOneMonthFromNow = new Date(dateNow);
    dateOneMonthFromNow.setMonth(dateNow.getMonth() + 1);
    console.log(dateOneMonthFromNow.toISOString()); //outputs: "2020-12-01T00:00:00.000Z"

    spy.mockRestore();
}); 
matej bobaly
  • 456
  • 3
  • 6
13

You can override Date constructor with an mocked function which returns your a constructed Date object with a date value you specified:

var yourModule = require('./yourModule')

test('Mock Date', () => {
  const mockedDate = new Date(2017, 11, 10)
  const originalDate = Date

  global.Date = jest.fn(() => mockedDate)
  global.Date.setDate = originalDate.setDate

  expect(yourModule.sameTimeTomorrow().getDate()).toEqual(11)
})

You can test the example here: https://repl.it/@miluoshi5/jest-mock-date

Mildo
  • 131
  • 1
  • 5
3

You can replace the Date constructor with something that always returns a hardcoded date, and then put it back to normal when done.

var _Date = null;

function replaceDate() {
  if (_Date) {
    return
  };

  _Date = Date;

  Object.getOwnPropertyNames(Date).forEach(function(name) { 
    _Date[name] = Date[name] 
  });

  // set Date ctor to always return same date
  Date = function() { return new _Date('2000-01-01T00:00:00.000Z') }

  Object.getOwnPropertyNames(_Date).forEach(function(name) { 
    Date[name] = _Date[name] 
  });  
}

function repairDate() {
  if (_Date === null) {
    return;
  }

  Date = _Date;
  Object.getOwnPropertyNames(_Date).forEach(function(name) { 
    Date[name] = _Date[name] 
  });  

  _Date = null;
}

// test that two dates created at different times return the same timestamp
var t0 = new Date();

// create another one 100ms later
setTimeout(function() {
  var t1 = new Date();

  console.log(t0.getTime(), t1.getTime(), t0.getTime() === t1.getTime());

  // put things back to normal when done
  repairDate();
}, 100);
Ben Taber
  • 6,426
  • 1
  • 22
  • 17
2

In my case I had to mock the whole Date and 'now' function before test:

const mockedData = new Date('2020-11-26T00:00:00.000Z');

jest.spyOn(global, 'Date').mockImplementation(() => mockedData);

Date.now = () => 1606348800;

describe('test', () => {...})

Ridd
  • 10,701
  • 3
  • 19
  • 20
1

If you have more than one Date (either in multiple tests or multiple times in one test) you might need to do the following:

const OriginalDate = Date;

it('should stub multiple date instances', () => {
  jest.spyOn(global, 'Date');
  const date1: any = new OriginalDate(2021, 1, 18);
  (Date as any).mockImplementationOnce(mockDate(OriginalDate, date1));

  const date2: any = new OriginalDate(2021, 1, 19);
  (Date as any).mockImplementationOnce(mockDate(OriginalDate, date2));

  const actualDate1 = new Date();
  const actualDate2 = new Date();

  expect(actualDate1).toBe(date1);
  expect(actualDate2).toBe(date2);
});

function mockDate(OriginalDate: DateConstructor, date: any): any {
  return (aDate: string) => {
    if (aDate) {
      return new OriginalDate(aDate);
    }
    return date;
  };
}

Also see this answer


Original Answer:

I just wrote a jest test and was able to stub new Date() with global.Date = () => now

David
  • 4,191
  • 2
  • 31
  • 40
1

You can use date-faker to mock what new Date() or Date.now() returns.

import { dateFaker } from 'date-faker'; // var { dateFaker } = require('date-faker');

// will return tomorrow, shift by one unit
dateFaker.add(1, 'day'); 

// shift by several units
dateFaker.add({ year: 1, month: -2, day: 3 });

// set up specific date, accepts Date or time string
dateFaker.set('2019/01/24'); 

dateFaker.reset();
MatGar
  • 11
  • 3
1

Simply do this:

it('should mock Date and its methods', () => {
    const mockDate = new Date('14 Oct 1995')
    global.Date = jest.fn().mockImplementation(() => mockDate)
    Date.prototype.setHours = jest.fn().mockImplementation((hours) => hours)
    Date.prototype.getHours = jest.fn().mockReturnValue(1)
}

it's working for me

Duc Trung Mai
  • 2,141
  • 1
  • 24
  • 23
1

Another way for older jest versions might be to create a Date subclass.

I only needed to mock the current date for my use case. The code under test uses new Date() and a lot of the real Date methods.

class MockDate extends Date {
  constructor(arg) {
    // use arg normally if provided, 
    // otherwise default to mock date: 2022-06-16T01:02:03.004
    super(arg || 1655341323004);
  }
}

global.Date = MockDate;
zord
  • 4,538
  • 2
  • 25
  • 30
1

For those who simply need to have a static date when calling new Date(), it's possible to fake the system time with jest:

/**
 * Fakes system time with the given timestamp.
 * Don't forget to call `jest.useRealTimers()` after the test.
 */
export function setSystemTime(timestamp: number): void {
  jest.useFakeTimers()
  jest.setSystemTime(timestamp)
}

In beforeAll:

now = Date.now()
setSystemTime(now)

In afterAll:

jest.useRealTimers()
Pleymor
  • 2,611
  • 1
  • 32
  • 44
  • It works like a charm locally, but throws `TypeError` when tests are run in the CI pipeline: Cannot assign to read only property 'performance' of object '[object global]' – Pleymor Oct 31 '22 at 14:43
  • 1
    for v26, should be initialised like ```jest.useFakeTimers('modern');``` see @ThdK answer – Akshay Dec 17 '22 at 09:11
0

I'm using Typescript and the easiest implementaion I found was doing the following:

const spy = jest.spyOn(global, 'Date');  // spy on date
const date = spy.mock.instances[0];      // gets the date in string format

and then use new Date(date) for your tests

Harry Riley
  • 165
  • 1
  • 6
0

I faced an error by using solution of @ThdK for one of my projects with old version of jest - TypeError: setSystemTime is not available when not using modern timers. So i did next and it works for me

let dateNowSpy: jest.SpyInstance;
const today = new Date('2023-05-22').getTime();

describe('Some description', () => {
  beforeEach(() => {
    dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => today);
  });

  afterEach(() => {
    dateNowSpy.mockRestore();
  });

  test('some useful description', () => {
    /* Date.now() -> 2000-01-01 */
  });
});
Kas Elvirov
  • 7,394
  • 4
  • 40
  • 62
-3

Here's what I'm doing now and this is working and doesn't clutter my method's signature.

newDate.js

module.exports = function(){
  return new Date();
};

someModule.js

var newDate = require('newDate.js');
module.exports = {
  sameTimeTomorrow: function(){
    var dt = newDate();
        dt.setDate(dt.getDate() + 1);
    return dt;
  }
};

someModule-test.js

jest.dontMock('someModule.js');

describe('someModule', function(){

  it('sameTimeTomorrow', function(){
   var newDate = require('../../_app/util/newDate.js');
       newDate.mockReturnValue(new Date(2015, 02, 13, 09, 15, 40, 123));

   var someModule = require('someModule.js');

   expect(someModule.sameTimeTomorrow().toString()).toBe(new Date(2015, 02, 14, 09, 15, 40, 123).toString());
  });

});
Seth Feldkamp
  • 1,406
  • 2
  • 9
  • 8
  • 7
    This forces you to modify your application code just to do mocking. Not very optimal, especially for larger applications. – linuxdan Mar 08 '17 at 23:10