92

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
  • 6,747
  • 3
  • 32
  • 63
Seth Feldkamp
  • 956
  • 1
  • 6
  • 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

12 Answers12

121

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
  • 8,559
  • 23
  • 69
  • 96
  • 3
    Be aware that using fake timers affects `nock` and causes it to miss HTTP matches. I wasted a lot of time until I figured out fake times were the cause my specs failing. – Oded Peer Apr 19 '21 at 08:23
  • @OdedPeer That might be Node version dependant. Node > 15.7 might not be subject to it. Ref discussion on Node primordials: https://github.com/sinonjs/fake-timers/issues/344. FYI I am assuming `nock` might rely on the `http` internals. – oligofren Nov 04 '21 at 23:52
94

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
  • 25,247
  • 27
  • 110
  • 174
Yuci
  • 22,858
  • 8
  • 99
  • 108
  • 18
    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
  • 20
    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
26

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
  • 3,898
  • 3
  • 26
  • 37
12

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
  • 326
  • 2
  • 6
10

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
  • 101
  • 1
  • 5
2

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,098
  • 1
  • 20
  • 15
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
  • 3,265
  • 2
  • 23
  • 38
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
  • 1,471
  • 1
  • 15
  • 17
1

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
  • 7,921
  • 3
  • 17
  • 19
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

-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
  • 956
  • 1
  • 6
  • 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