How to mock a constructor like new Date()
JavascriptJestjsJavascript Problem Overview
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()
?
Javascript Solutions
Solution 1 - Javascript
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.
Solution 2 - Javascript
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.
Solution 3 - Javascript
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.
Solution 4 - Javascript
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();
});
Solution 5 - Javascript
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
Solution 6 - Javascript
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);
Solution 7 - Javascript
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
Solution 8 - Javascript
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();
Solution 9 - Javascript
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
Solution 10 - Javascript
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', () => {...})
Solution 11 - Javascript
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
Solution 12 - Javascript
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());
});
});