如何在 Jest 中设置模拟日期?
How do I set a mock date in Jest?
我正在使用 moment.js 在我的 React 组件的帮助文件中完成我的大部分日期逻辑,但我一直无法弄清楚如何在 Jest a la 中模拟日期 sinon.useFakeTimers()
.
Jest 文档只讨论 setTimeout
、setInterval
等计时器函数,但不会帮助设置日期,然后检查我的日期函数是否按预期执行.
这是我的一些 JS 文件:
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;
这是我使用 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);
});
});
});
现在这些测试通过了,因为我使用了 moment 并且我的函数使用了 moment 但它似乎有点不稳定,我想将日期设置为测试的固定时间。
知道如何实现吗?
MockDate 可用于笑话测试以更改 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();
由于 momentjs 在内部使用 Date
,您可以覆盖 Date.now
函数以始终 return 同一时刻。
Date.now = jest.fn(() => 1487076708000) //14.02.2017
或
Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())
对于快速和肮脏的解决方案,使用 jest.spyOn 锁定时间:
let dateNowSpy;
beforeAll(() => {
// Lock Time
dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => 1487076708000);
});
afterAll(() => {
// Unlock Time
dateNowSpy.mockRestore();
});
更新:
要获得更可靠的解决方案,请查看 timekeeper:
import timekeeper from 'timekeeper';
beforeAll(() => {
// Lock Time
timekeeper.freeze(new Date('2014-01-01'));
});
afterAll(() => {
// Unlock Time
timekeeper.reset();
});
所有仅基于 Date.now()
模拟的答案都不会适用于所有地方,因为某些包(例如 moment.js
)使用 new Date()
。
在这种情况下,我认为基于 MockDate
的答案是唯一正确的。如果你不想使用外部包,你可以直接在你的 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;
jest-date-mock是我写的一个完整的javascript模块,用来测试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
});
使用仅有的 3 个 api 作为测试用例。
- advanceBy(ms): 以毫秒为单位提前日期时间戳。
- advanceTo([timestamp]): 将日期重置为时间戳,默认为0。
- clear(): 关闭模拟系统。
我想提供一些替代方法。
如果您需要存根 format()
(这可能取决于语言环境和时区!)
import moment from "moment";
...
jest.mock("moment");
...
const format = jest.fn(() => 'April 11, 2019')
moment.mockReturnValue({ format })
如果只需要存根moment()
:
import moment from "moment";
...
jest.mock("moment");
...
const now = "moment(\"2019-04-11T09:44:57.299\")";
moment.mockReturnValue(now);
关于上面 isDateToday
函数的测试,我认为最简单的方法是根本不模拟 moment
我想使用手动模拟,这样它可以在所有测试中使用。
// <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
目标是在组件呈现期间使用固定日期模拟 new Date() 以用于测试目的。如果您唯一想要的是模拟 new Date() fn.
,那么使用库将是一项开销
想法是将全局日期存储到临时变量,模拟全局日期,然后在使用后将临时值重新分配给全局日期。
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();
});
});
我只是想在这里插话,因为如果你只想在特定套件中模拟 Date
对象,则没有答案解决这个问题。
您可以使用每个套件的设置和拆卸方法模拟它,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;
});
希望对您有所帮助!
对于那些想要在 new Date
对象上模拟方法的人,您可以执行以下操作:
beforeEach(() => {
jest.spyOn(Date.prototype, 'getDay').mockReturnValue(2);
jest.spyOn(Date.prototype, 'toISOString').mockReturnValue('2000-01-01T00:00:00.000Z');
});
afterEach(() => {
jest.restoreAllMocks()
});
这就是我如何嘲笑我的 Date.now()
方法来将我的测试年份设置为 2010
jest
.spyOn(global.Date, 'now')
.mockImplementationOnce(() => new Date(`2010`).valueOf());
您可以使用 date-faker。让您相对更改当前日期:
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();
这里有一些适用于不同用例的可读方法。我更喜欢使用间谍而不是保存对原始对象的引用,原始对象可能会被其他代码意外覆盖。
一次性嘲讽
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();
});
从 Jest 26 开始,这可以使用“现代”假计时器来实现,而无需安装任何第 3 方模块:https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers
jest
.useFakeTimers()
.setSystemTime(new Date('2020-01-01'));
如果你想让假计时器在 所有 测试中处于活动状态,你可以在你的配置中设置 timers: 'modern'
:https://jestjs.io/docs/configuration#timers-string
编辑:从 Jest 27 开始,现代假计时器是默认设置,因此您可以将参数删除到 useFakeTimers
。
这对我有用:
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
在我的例子中,我必须在测试前模拟整个 Date 和 'now' 函数:
const mockedData = new Date('2020-11-26T00:00:00.000Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockedData);
Date.now = () => 1606348800;
describe('test', () => {...})
我发现的最好方法就是用您正在使用的任何函数覆盖原型。
Date.prototype.getTimezoneOffset = function () {
return 456;
};
Date.prototype.getTime = function () {
return 123456;
};
稍微改进@pranava-s-balugari 的响应
- 不影响
new Date(something)
- 可以更改模拟日期。
- 它也适用于 Date.now
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'));
})
})
以下测试存根 return 测试生命周期中的一个常量。
如果你在你的项目中使用了 new Date()
那么你可以在你的测试文件中像这样模拟它:
beforeEach(async () => {
let time_now = Date.now();
const _GLOBAL: any = global;
_GLOBAL.Date = class {
public static now() {
return time_now;
}
};
}
现在,无论您在测试文件中使用 new Date()
,它都会产生相同的时间戳。
注意:您可以将 beforeEach
替换为 beforeAll
。而_GLOBAL
只是一个满足typescript的代理变量。
我试过的完整代码:
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);
});
});
它对我有用。
接受的答案很好 -
Date.now = jest.fn().mockReturnValue(new Date('2021-08-29T18:16:19+00:00'));
但是如果我们想 运行 在管道中进行单元测试,我们必须确保我们使用相同的时区。为此,我们还必须模拟时区 -
jest.config.js
process.env.TZ = 'GMT';
module.exports = {
...
};
我正在使用 moment + moment-timezone,其中 none 对我有用。
这有效:
jest.mock('moment', () => {
const moment = jest.requireActual('moment');
moment.now = () => +new Date('2022-01-18T12:33:37.000Z');
return moment;
});
我正在使用 moment.js 在我的 React 组件的帮助文件中完成我的大部分日期逻辑,但我一直无法弄清楚如何在 Jest a la 中模拟日期 sinon.useFakeTimers()
.
Jest 文档只讨论 setTimeout
、setInterval
等计时器函数,但不会帮助设置日期,然后检查我的日期函数是否按预期执行.
这是我的一些 JS 文件:
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;
这是我使用 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);
});
});
});
现在这些测试通过了,因为我使用了 moment 并且我的函数使用了 moment 但它似乎有点不稳定,我想将日期设置为测试的固定时间。
知道如何实现吗?
MockDate 可用于笑话测试以更改 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();
由于 momentjs 在内部使用 Date
,您可以覆盖 Date.now
函数以始终 return 同一时刻。
Date.now = jest.fn(() => 1487076708000) //14.02.2017
或
Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())
对于快速和肮脏的解决方案,使用 jest.spyOn 锁定时间:
let dateNowSpy;
beforeAll(() => {
// Lock Time
dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => 1487076708000);
});
afterAll(() => {
// Unlock Time
dateNowSpy.mockRestore();
});
更新:
要获得更可靠的解决方案,请查看 timekeeper:
import timekeeper from 'timekeeper';
beforeAll(() => {
// Lock Time
timekeeper.freeze(new Date('2014-01-01'));
});
afterAll(() => {
// Unlock Time
timekeeper.reset();
});
所有仅基于 Date.now()
模拟的答案都不会适用于所有地方,因为某些包(例如 moment.js
)使用 new Date()
。
在这种情况下,我认为基于 MockDate
的答案是唯一正确的。如果你不想使用外部包,你可以直接在你的 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;
jest-date-mock是我写的一个完整的javascript模块,用来测试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
});
使用仅有的 3 个 api 作为测试用例。
- advanceBy(ms): 以毫秒为单位提前日期时间戳。
- advanceTo([timestamp]): 将日期重置为时间戳,默认为0。
- clear(): 关闭模拟系统。
我想提供一些替代方法。
如果您需要存根 format()
(这可能取决于语言环境和时区!)
import moment from "moment";
...
jest.mock("moment");
...
const format = jest.fn(() => 'April 11, 2019')
moment.mockReturnValue({ format })
如果只需要存根moment()
:
import moment from "moment";
...
jest.mock("moment");
...
const now = "moment(\"2019-04-11T09:44:57.299\")";
moment.mockReturnValue(now);
关于上面 isDateToday
函数的测试,我认为最简单的方法是根本不模拟 moment
我想使用手动模拟,这样它可以在所有测试中使用。
// <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
目标是在组件呈现期间使用固定日期模拟 new Date() 以用于测试目的。如果您唯一想要的是模拟 new Date() fn.
,那么使用库将是一项开销想法是将全局日期存储到临时变量,模拟全局日期,然后在使用后将临时值重新分配给全局日期。
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();
});
});
我只是想在这里插话,因为如果你只想在特定套件中模拟 Date
对象,则没有答案解决这个问题。
您可以使用每个套件的设置和拆卸方法模拟它,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;
});
希望对您有所帮助!
对于那些想要在 new Date
对象上模拟方法的人,您可以执行以下操作:
beforeEach(() => {
jest.spyOn(Date.prototype, 'getDay').mockReturnValue(2);
jest.spyOn(Date.prototype, 'toISOString').mockReturnValue('2000-01-01T00:00:00.000Z');
});
afterEach(() => {
jest.restoreAllMocks()
});
这就是我如何嘲笑我的 Date.now()
方法来将我的测试年份设置为 2010
jest
.spyOn(global.Date, 'now')
.mockImplementationOnce(() => new Date(`2010`).valueOf());
您可以使用 date-faker。让您相对更改当前日期:
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();
这里有一些适用于不同用例的可读方法。我更喜欢使用间谍而不是保存对原始对象的引用,原始对象可能会被其他代码意外覆盖。
一次性嘲讽
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();
});
从 Jest 26 开始,这可以使用“现代”假计时器来实现,而无需安装任何第 3 方模块:https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers
jest
.useFakeTimers()
.setSystemTime(new Date('2020-01-01'));
如果你想让假计时器在 所有 测试中处于活动状态,你可以在你的配置中设置 timers: 'modern'
:https://jestjs.io/docs/configuration#timers-string
编辑:从 Jest 27 开始,现代假计时器是默认设置,因此您可以将参数删除到 useFakeTimers
。
这对我有用:
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
在我的例子中,我必须在测试前模拟整个 Date 和 'now' 函数:
const mockedData = new Date('2020-11-26T00:00:00.000Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockedData);
Date.now = () => 1606348800;
describe('test', () => {...})
我发现的最好方法就是用您正在使用的任何函数覆盖原型。
Date.prototype.getTimezoneOffset = function () {
return 456;
};
Date.prototype.getTime = function () {
return 123456;
};
稍微改进@pranava-s-balugari 的响应
- 不影响
new Date(something)
- 可以更改模拟日期。
- 它也适用于 Date.now
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'));
})
})
以下测试存根 return 测试生命周期中的一个常量。
如果你在你的项目中使用了 new Date()
那么你可以在你的测试文件中像这样模拟它:
beforeEach(async () => {
let time_now = Date.now();
const _GLOBAL: any = global;
_GLOBAL.Date = class {
public static now() {
return time_now;
}
};
}
现在,无论您在测试文件中使用 new Date()
,它都会产生相同的时间戳。
注意:您可以将 beforeEach
替换为 beforeAll
。而_GLOBAL
只是一个满足typescript的代理变量。
我试过的完整代码:
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);
});
});
它对我有用。
接受的答案很好 -
Date.now = jest.fn().mockReturnValue(new Date('2021-08-29T18:16:19+00:00'));
但是如果我们想 运行 在管道中进行单元测试,我们必须确保我们使用相同的时区。为此,我们还必须模拟时区 -
jest.config.js
process.env.TZ = 'GMT';
module.exports = {
...
};
我正在使用 moment + moment-timezone,其中 none 对我有用。
这有效:
jest.mock('moment', () => {
const moment = jest.requireActual('moment');
moment.now = () => +new Date('2022-01-18T12:33:37.000Z');
return moment;
});