Reputation: 4958
I'm using moment.js to do most of my date logic in a helper file for my React components but I haven't been able to figure out how to mock a date in Jest a la sinon.useFakeTimers()
.
The Jest docs only speak about timer functions like setTimeout
, setInterval
etc but don't help with setting a date and then checking that my date functions do what they're meant to do.
Here is some of my JS file:
var moment = require('moment');
var DateHelper = {
DATE_FORMAT: 'MMMM D',
API_DATE_FORMAT: 'YYYY-MM-DD',
formatDate: function(date) {
return date.format(this.DATE_FORMAT);
},
isDateToday: function(date) {
return this.formatDate(date) === this.formatDate(moment());
}
};
module.exports = DateHelper;
and here is what I've set up using Jest:
jest.dontMock('../../../dashboard/calendar/date-helper')
.dontMock('moment');
describe('DateHelper', function() {
var DateHelper = require('../../../dashboard/calendar/date-helper'),
moment = require('moment'),
DATE_FORMAT = 'MMMM D';
describe('formatDate', function() {
it('should return the date formatted as DATE_FORMAT', function() {
var unformattedDate = moment('2014-05-12T00:00:00.000Z'),
formattedDate = DateHelper.formatDate(unformattedDate);
expect(formattedDate).toEqual('May 12');
});
});
describe('isDateToday', function() {
it('should return true if the passed in date is today', function() {
var today = moment();
expect(DateHelper.isDateToday(today)).toEqual(true);
});
});
});
Now these tests pass because I'm using moment and my functions use moment but it seems a bit unstable and I would like to set the date to a fixed time for the tests.
Any idea on how that could be accomplished?
Upvotes: 375
Views: 380268
Reputation: 2791
If you are using vitest
would be pretty easy:
vi.setSystemTime("2024-06-12T11:00:00.000Z")
vi
VitestUtils
manage all the things related to Date object so we don't need to mock and make dark magic under the hood.
Some examples in their docs: https://vitest.dev/guide/mocking#dates
Upvotes: -1
Reputation: 17131
It would be good to set it before each test.
describe('TestCase', () => {
beforeAll(() => {
// Lock Time
// new Date(). It helps us to achieve dynamic a date rather than hard code.
jest.useFakeTimers().setSystemTime(new Date());
});
});
Upvotes: 1
Reputation: 2001
I ran into issues with most implementations.
While jest.setSystemTime(new Date(date));
is attractive, in practice, it was not overriding the date in many circumstances.
Just overriding Date.now
is OK, but I also use new Date
throughout the codebase, as does moment
.
Finally, I felt that a package was simply overkill.
After much trial and error, taking some inspiration from the answers here, here is my solution, which inherits from Date, so it will have all it's properties, and will account for different types of parameters (such as multiple parameters, without which moment().startOf('day')
wasn't working).
// default mock date, if you want one
export const setDateToReturnMockDate = (date) => {
const mockDate = new Date(date);
const _Date = Date;
class MockDate extends _Date {
// can accept an array, e.g. new Date(2023, 3, 2);
constructor(...date) {
return date.length ? new _Date(...date) : mockDate;
}
}
MockDate.now = () => +mockDate;
global.Date = MockDate;
};
Upvotes: 2
Reputation: 7172
To mock toISOString
you can do:
jest.spyOn(global.Date.prototype, 'toISOString').mockReturnValue('2023-09-06T11:54:47.050Z')
Upvotes: 7
Reputation: 2158
Sometimes, using just
jest.useFakeTimers({
now: 1673445238335,
});
leads to freezes in async/await functions. To avoid freezes, I don't mock some APIs:
jest.useFakeTimers({
doNotFake: [
"setImmediate",
"clearImmediate",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"nextTick",
"queueMicrotask",
],
now: new Date(TEST_DATE),
});
Upvotes: 5
Reputation: 4001
Using Jest fake timers may add additional complexity when working with async code: jest.useFakeTimers
overrides the global setTimeout
which is used in RTL waitFor
, waitForNextUpdate
and similar. Adding a jest spy could solve this:
import jest from 'jest';
jest.spyOn(Date, 'now');
const now = Date.now(); // mocked `Date.now() value
If you want to use a specific mocked date:
const mockDateNow = 1689975660000
jest.spyOn(Date, 'now').mockReturnedValue(mockDateNow);
Upvotes: 0
Reputation: 5962
jest.useFakeTimers({ now: Number(new Date()) })
Jest docs: https://jestjs.io/docs/timer-mocks
Upvotes: 0
Reputation: 260
jest.useFakeTimers('modern').setSystemTime(new Date('2023-03-01'))
Upvotes: 1
Reputation: 28111
For a robust solution, look at timekeeper:
import timekeeper from 'timekeeper';
beforeAll(() => {
// Lock Time
timekeeper.freeze(new Date('2014-01-01'));
});
afterAll(() => {
// Unlock Time
timekeeper.reset();
});
For older versions of Jest:
For quick and dirty solution use jest.spyOn for locking time:
let dateNowSpy;
beforeAll(() => {
// Lock Time
dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => 1487076708000);
});
afterAll(() => {
// Unlock Time
dateNowSpy.mockRestore();
});
Upvotes: 165
Reputation: 3306
Since Jest
version 29
you can do the following as well:
jest.useFakeTimers({
now: 1673445238335,
});
The following options are allowed:
type FakeTimersConfig = {
/**
* If set to `true` all timers will be advanced automatically by 20 milliseconds
* every 20 milliseconds. A custom time delta may be provided by passing a number.
* The default is `false`.
*/
advanceTimers?: boolean | number;
/**
* List of names of APIs that should not be faked. The default is `[]`, meaning
* all APIs are faked.
*/
doNotFake?: Array<FakeableAPI>;
/**
* Use the old fake timers implementation instead of one backed by `@sinonjs/fake-timers`.
* The default is `false`.
*/
legacyFakeTimers?: boolean;
/** Sets current system time to be used by fake timers. The default is `Date.now()`. */
now?: number | Date;
/**
* The maximum number of recursive timers that will be run when calling `jest.runAllTimers()`.
* The default is `100_000` timers.
*/
timerLimit?: number;
};
You can read more in the docs.
Upvotes: 6
Reputation: 5025
I was using an external library and to make it work I had to run this code on the setup stage:
Date.now = jest.fn(() => new Date(Date.UTC(2021, 2, 30)).valueOf());
I wrote this in my setupTests.ts
file set in the setupFilesAfterEnv
prop from jest.config.js
:
module.exports = {
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
};
Upvotes: 1
Reputation: 4279
I recommend sinonjs/fake-timers
. It's very similar to the fake timer provided by jest
, but much more user-friendly.
import FakeTimers from '@sinonjs/fake-timers';
const clock = FakeTimers.install()
clock.setSystemTime(new Date('2022-01-01'));
console.log(new Date()) // 2020-01-01T00:00:00.000Z
Upvotes: 0
Reputation: 7449
As of Jest 26 this can be achieved using "modern" fake timers without needing to install any 3rd party modules: https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers
jest
.useFakeTimers()
.setSystemTime(new Date('2020-01-01'));
If you want the fake timers to be active for all tests, you can set timers: 'modern'
in your configuration: https://jestjs.io/docs/configuration#timers-string
EDIT: As of Jest 27 modern fake timers is the default, so you can drop the argument to useFakeTimers
.
Upvotes: 519
Reputation: 27497
I'm using moment + moment-timezone and none of these worked for me.
This worked:
jest.mock('moment', () => {
const moment = jest.requireActual('moment');
moment.now = () => +new Date('2022-01-18T12:33:37.000Z');
return moment;
});
Upvotes: 3
Reputation: 11
Goal is to mock new Date() with a fixed date wherever it's used during the component rendering for test purposes. Using libraries will be an overhead if the only thing you want is to mock new Date() fn.
Idea is to store the global date to a temp variable, mock the global date and then after usage reassign temp to global date.
export const stubbifyDate = (mockedDate: Date) => {
/**
* Set Date to a new Variable
*/
const MockedRealDate = global.Date;
/**
* Mock Real date with the date passed from the test
*/
(global.Date as any) = class extends MockedRealDate {
constructor() {
super()
return new MockedRealDate(mockedDate)
}
}
/**
* Reset global.Date to original Date (MockedRealDate) after every test
*/
afterEach(() => {
global.Date = MockedRealDate
})
}
Usage in your test would be like
import { stubbyifyDate } from './AboveMethodImplementedFile'
describe('<YourComponent />', () => {
it('renders and matches snapshot', () => {
const date = new Date('2019-02-18')
stubbifyDate(date)
const component = renderer.create(
<YourComponent data={}/>
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
Upvotes: 0
Reputation: 5250
For those who want to mock methods on a new Date
object you can do the following:
beforeEach(() => {
jest.spyOn(Date.prototype, 'getDay').mockReturnValue(2);
jest.spyOn(Date.prototype, 'toISOString').mockReturnValue('2000-01-01T00:00:00.000Z');
});
afterEach(() => {
jest.restoreAllMocks()
});
Upvotes: 45
Reputation: 15204
The accepted answer works good -
Date.now = jest.fn().mockReturnValue(new Date('2021-08-29T18:16:19+00:00'));
But if we want to run unit tests in pipeline we have to make sure we are using the same time zone. To do that we have to mock timezone as well -
jest.config.js
process.env.TZ = 'GMT';
module.exports = {
...
};
See also: the full list of timezones (column TZ database name)
Upvotes: 1
Reputation: 7150
The following test stubs Date to return a constant during the test lifecycle.
If you have use new Date()
in your project then you could mock it in your test file something like this:
beforeEach(async () => {
let time_now = Date.now();
const _GLOBAL: any = global;
_GLOBAL.Date = class {
public static now() {
return time_now;
}
};
}
Now wherever you will use new Date()
in your test file, It will produce the same timestamp.
Note: you could replace beforeEach
with beforeAll
. And _GLOBAL
is just a proxy variable to satisfy typescript.
The complete code I tried:
let time_now;
const realDate = Date;
describe("Stubbed Date", () => {
beforeAll(() => {
timeNow = Date.now();
const _GLOBAL: any = global;
_GLOBAL.Date = class {
public static now() {
return time_now;
}
constructor() {
return time_now;
}
public valueOf() {
return time_now;
}
};
});
afterAll(() => {
global.Date = realDate;
});
it("should give same timestamp", () => {
const date1 = Date.now();
const date2 = new Date();
expect(date1).toEqual(date2);
expect(date2).toEqual(time_now);
});
});
It worked for me.
Upvotes: 0
Reputation: 1077
Improving a bit the @pranava-s-balugari response
new Date(something)
const DateOriginal = global.Date;
global.Date = class extends DateOriginal {
constructor(params) {
if (params) {
super(params)
} else if (global.Date.NOW === undefined) {
super()
} else {
super(global.Date.NOW)
}
}
static now () {
return new Date().getTime();
}
}
afterEach(() => {
global.Date.NOW = undefined;
})
afterAll(() => {
global.Date = DateOriginal;
});
describe('some test', () => {
afterEach(() => NOW = undefined);
it('some test', () => {
Date.NOW = '1999-12-31T23:59:59' // or whatever parameter you could pass to new Date([param]) to get the date you want
expect(new Date()).toEqual(new Date('1999-12-31T23:59:59'));
expect(new Date('2000-01-01')).toEqual(new Date('2000-01-01'));
expect(Date.now()).toBe(946681199000)
Date.NOW = '2020-01-01'
expect(new Date()).toEqual(new Date('2020-01-01'));
})
})
Upvotes: 1
Reputation: 273
Best way I have found is just to override the prototype with whatever function you are using.
Date.prototype.getTimezoneOffset = function () {
return 456;
};
Date.prototype.getTime = function () {
return 123456;
};
Upvotes: 0
Reputation: 11897
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', () => {...})
Upvotes: 1
Reputation: 2558
This works for me:
const mockDate = new Date('14 Oct 1995')
global.Date = jest.fn().mockImplementation(() => mockDate) // mock Date "new" constructor
global.Date.now = jest.fn().mockReturnValue(mockDate.valueOf()) // mock Date.now
Upvotes: 10
Reputation: 53119
Here are a few readable ways for different use cases. I prefer using spies over saving references to the original objects, which can be accidentally overwritten in some other code.
jest
.spyOn(global.Date, 'now')
.mockImplementationOnce(() => Date.parse('2020-02-14'));
let dateSpy;
beforeAll(() => {
dateSpy = jest
.spyOn(global.Date, 'now')
.mockImplementation(() => Date.parse('2020-02-14'));
});
afterAll(() => {
dateSpy.mockRestore();
});
Upvotes: 10
Reputation: 11
You can use date-faker. Lets you change the current date relatively:
import { dateFaker } from 'date-faker';
// or require if you wish: var { dateFaker } = require('date-faker');
// make current date to be tomorrow
dateFaker.add(1, 'day'); // 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond'.
// change using many units
dateFaker.add({ year: 1, month: -2, day: 3 });
// set specific date, type: Date or string
dateFaker.set('2019/01/24');
// reset
dateFaker.reset();
Upvotes: 0
Reputation: 155
This is how I mocked my Date.now()
method to set the year to 2010 for my test
jest
.spyOn(global.Date, 'now')
.mockImplementationOnce(() => new Date(`2010`).valueOf());
Upvotes: 5
Reputation: 480
I just wanted to chime in here since no answer addressed the issue if you want to mock the Date
object in only a specific suite.
You can mock it using the setup and teardown methods for each suite, jest docs
/**
* Mocking Date for this test suite
*/
const globalDate = Date;
beforeAll(() => {
// Mocked Date: 2020-01-08
Date.now = jest.fn(() => new Date(Date.UTC(2020, 0, 8)).valueOf());
});
afterAll(() => {
global.Date = globalDate;
});
Hope this helps!
Upvotes: 0
Reputation: 3745
Since momentjs uses Date
internally, you can just overwrite the Date.now
function to always return the same moment.
Date.now = jest.fn(() => 1487076708000) //14.02.2017
or
Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())
Upvotes: 234
Reputation: 580
I'd like use Manual Mocks, so it can use in all tests.
// <rootDir>/__mocks__/moment.js
const moment = jest.requireActual('moment')
Date.now = jest.fn(() => 1558281600000) // 2019-05-20 00:00:00.000+08:00
module.exports = moment
Upvotes: 1
Reputation: 4837
I would like to offer some alternative approaches.
If you need to stub format()
(which can be locale and timezone dependent!)
import moment from "moment";
...
jest.mock("moment");
...
const format = jest.fn(() => 'April 11, 2019')
moment.mockReturnValue({ format })
If you only need to stub moment()
:
import moment from "moment";
...
jest.mock("moment");
...
const now = "moment(\"2019-04-11T09:44:57.299\")";
moment.mockReturnValue(now);
Regarding the test for the isDateToday
function above, I believe the simplest way would be not to mock moment
at all
Upvotes: 1
Reputation: 496
jest-date-mock is a complete javascript module wrote by me, and it is used to test Date on jest.
import { advanceBy, advanceTo } from 'jest-date-mock';
test('usage', () => {
advanceTo(new Date(2018, 5, 27, 0, 0, 0)); // reset to date time.
const now = Date.now();
advanceBy(3000); // advance time 3 seconds
expect(+new Date() - now).toBe(3000);
advanceBy(-1000); // advance time -1 second
expect(+new Date() - now).toBe(2000);
clear();
Date.now(); // will got current timestamp
});
Use the only 3 api for test cases.
Upvotes: 8