如何模拟像 new Date() 这样的构造函数
Posted
技术标签:
【中文标题】如何模拟像 new Date() 这样的构造函数【英文标题】:How to mock a constructor like new Date() 【发布时间】:2015-04-14 18:53:07 【问题描述】:我有一个方法,它依赖于new Date
创建一个日期对象,然后对其进行操作。我正在测试操作是否按预期工作,因此我需要将返回的日期与预期日期进行比较。为了做到这一点,我需要确保new Date
在测试和正在测试的方法中返回相同的值。我该怎么做?
有没有办法真正模拟构造函数的返回值?
我可以创建一个模块,该模块可能需要一个提供日期对象并且可以模拟的函数。但这在我的代码中似乎是不必要的抽象。
要测试的示例函数...
module.exports =
sameTimeTomorrow: function()
var dt = new Date();
dt.setDate(dt + 1);
return dt;
;
如何模拟new Date()
的返回值?
【问题讨论】:
我有点不确定。能发下代码吗?我的猜测是您应该将definedDate
传递给这两个函数,并且当您分配日期时您应该执行var assignedDate = definedDate || new Date();
来检查您是否通过了测试值。然后你可以通过传递/不传递来启用/禁用它。
更新了一个(人为的)示例。我确实可以添加一个可选的日期参数并将其传递给我的测试。然而,这不是一个好的 api,而且我的真实用例已经有 3 个参数。所以我不热衷于添加另一个,尤其是因为它可能会严重混淆该方法的意图。
【参考方案1】:
这就是我现在正在做的事情,它正在工作并且不会弄乱我的方法的签名。
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());
);
);
【讨论】:
这迫使您修改应用程序代码只是为了进行模拟。不是很理想,尤其是对于大型应用程序。【参考方案2】:您可以使用 jasmine 的 spyOn(jest 是基于 jasmine 构建的)来模拟 Date 的 getDate 原型,如下所示:
spyOn(Date.prototype, 'setDate').and.returnValue(DATE_TO_TEST_WITH);
SpyOn 也会在它自身之后进行清理,并且仅在测试范围内持续。
【讨论】:
【参考方案3】:您可以将 Date 构造函数替换为始终返回硬编码日期的东西,然后在完成后将其恢复正常。
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);
【讨论】:
【参考方案4】:您可以使用模拟函数覆盖 Date 构造函数,该函数返回您构造的 Date 对象以及您指定的日期值:
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)
)
您可以在此处测试示例:https://repl.it/@miluoshi5/jest-mock-date
【讨论】:
【参考方案5】:如果您有多个日期(在多个测试中或在一个测试中多次),您可能需要执行以下操作:
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;
;
另见this answer
原答案:
我刚刚写了一个笑话测试,并且能够用global.Date = () => now
存根new Date()
【讨论】:
【参考方案6】:更新:此答案是jest < version 26
的方法,请参阅this answer for recent jest versions。
您可以使用jest.spyOn
模拟像 new Date() 这样的构造函数,如下所示:
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())
)
输出如下:
Normal: 1566424897579
Mocked: 1466424490000
Restored: 1566424897608
见the reference project on GitHub。
注意:如果您使用 TypeScript 并且会遇到编译错误,Argument of type '() => Date' is not assignable to parameter of type '() => string'. Type 'Date' is not assignable to type 'string'
。在这种情况下,一种解决方法是使用mockdate 库,该库可用于更改“现在”的时间。详情请见this question。
【讨论】:
我收到Argument of type '() => Date' is not assignable to parameter of type '() => string'. Type 'Date' is not assignable to type 'string'.
@EllaSharakanski 查看我的参考项目:github.com/yucigou/***-ref-projects/tree/master/…
@Yuci 谢谢,但我和你做的一模一样,但遇到了错误。那么可能与TypeScript有关。我在另一篇文章中问了我的问题:***.com/questions/60912023/…
@EllaSharakanski 查看我对您问题的回复:***.com/questions/60912023/…
对于TS,你可以骗Jest和TS:.mockImplementation(() => mockDate as unknown as string);
。像expect(new Date()).toBe(mockDate);
这样的测试工作......【参考方案7】:
您可以使用date-faker 模拟 new Date() 或 Date.now() 返回的内容。
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();
【讨论】:
【参考方案8】:我正在使用 Typescript,我发现最简单的实现是执行以下操作:
const spy = jest.spyOn(global, 'Date'); // spy on date
const date = spy.mock.instances[0]; // gets the date in string format
然后使用new Date(date)
进行测试
【讨论】:
【参考方案9】:虽然其他答案解决了这个问题,但我发现它更自然且通常适用于仅模拟 Date 的“无参数构造函数”行为,同时保持 Date 的其他功能不变。例如,当 ISO 日期字符串传递给构造函数时,期望返回此特定日期而不是模拟日期可能是合理的。
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();
);
【讨论】:
太棒了!这是唯一一个处理我的案例的模拟,它同时使用新日期和新日期(previousDate) 在这种情况下如何模拟Date.now()
?
模拟 Date.now() 只需使用: Date.now = jest.fn(() => new Date(2020, 9, 5))【参考方案10】:
只需这样做:
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)
它对我有用
【讨论】:
Jest 不建议这样做。您应该将 Jest Spies 与全局方法一起使用。 我确实试过了,但在我的情况下它不起作用(我正在使用 Nest),然后我就用这个【参考方案11】:在我的例子中,我必须在测试之前模拟整个 Date 和 'now' 函数:
const mockedData = new Date('2020-11-26T00:00:00.000Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockedData);
Date.now = () => 1606348800;
describe('test', () => ...)
【讨论】:
【参考方案12】:从 jest 26 开始,您可以使用支持 jest.setSystemTime
方法的“现代”fakeTimers 实现 (see article here)。
beforeAll(() =>
jest.useFakeTimers('modern');
jest.setSystemTime(new Date(2020, 3, 1));
);
afterAll(() =>
jest.useRealTimers();
);
请注意,'modern'
将是 jest 版本 27 的默认实现。
请参阅setSystemTime
here 的文档。
【讨论】:
请注意,使用假计时器会影响nock
并导致它错过 HTTP 匹配。我浪费了很多时间,直到我发现假时间是我的规格失败的原因。
@OdedPeer 这可能取决于节点版本。 Node > 15.7 可能不受其约束。参考关于 Node primordials 的讨论:github.com/sinonjs/fake-timers/issues/344。仅供参考,我假设 nock
可能依赖于 http
内部结构。以上是关于如何模拟像 new Date() 这样的构造函数的主要内容,如果未能解决你的问题,请参考以下文章
c++构造函数中使用new,析构函数用delete删除,出错