可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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, setInveral 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?
回答1:
MockDate can be used in jest tests to change what new Date()
returns:
var MockDate = require('mockdate');
// I use a timestamp to make sure the date stays fixed to the ms
MockDate.set(1434319925275);
// test code here
// reset to native Date()
MockDate.reset();
回答2:
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())
回答3:
jest.spyOn works for locking time:
let dateNowSpy;
beforeAll(() => {
// Lock Time
dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => 1487076708000);
});
afterAll(() => {
// Unlock Time
dateNowSpy.mockRestore();
});
回答4:
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.
- advanceBy(ms): advance date timestamp by ms.
- advanceTo([timestamp]): reset date to timestamp, default to 0.
- clear(): shut down the mock system.
回答5:
All the answer based only on the mock of Date.now()
will not work everywhere since some packages (for instance moment.js
) use new Date()
instead.
In this context the answer based on MockDate
is I think the only truly correct. If you don't want to use an external package, you can write directly in your beforeAll
:
const DATE_TO_USE = new Date('2017-02-02T12:54:59.218Z');
// eslint-disable-next-line no-underscore-dangle
const _Date = Date;
const MockDate = (...args) => {
switch (args.length) {
case 0:
return DATE_TO_USE;
default:
return new _Date(...args);
}
};
MockDate.UTC = _Date.UTC;
MockDate.now = () => DATE_TO_USE.getTime();
MockDate.parse = _Date.parse;
MockDate.toString = _Date.toString;
MockDate.prototype = _Date.prototype;
global.Date = MockDate;
回答6:
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
回答7:
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
回答8:
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 a 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 dae 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();
});
});
回答9:
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!
回答10:
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.restoreAll()
});