如何模拟像New Date()这样的构造函数

时间:2015-02-13 16:47:12

标签: javascript jestjs

我有一个依赖new Date创建日期对象然后操纵它的方法。我测试操作是否按预期工作,因此我需要将返回的日期与预期日期进行比较。为了做到这一点,我需要确保new Date在测试和被测试的方法中返回相同的值。我怎么能这样做?

有没有办法实际模拟构造函数的返回值?

我可以使用提供日期对象的函数创建一个模块,并且可以进行模拟。但这似乎是我代码中不必要的抽象。

要测试的示例函数......

module.exports = {
  sameTimeTomorrow: function(){
    var dt = new Date();
        dt.setDate(dt + 1);
    return dt;
  }
};

如何模拟new Date()的返回值?

12 个答案:

答案 0 :(得分:31)

从 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 的文档。

答案 1 :(得分:6)

您可以使用jasmine的spyOn(jest建立在jasmine上)来模拟Date的getDate原型,如下所示:

spyOn(Date.prototype, 'setDate').and.returnValue(DATE_TO_TEST_WITH);

SpyOn也会在自我清理后进行清理,并且只能持续测试范围。

答案 2 :(得分:6)

您可以使用模拟函数覆盖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

答案 3 :(得分:5)

您可以使用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

答案 4 :(得分:3)

尽管其他答案可以解决问题,但我发现它更自然,通常适用于仅模拟Date的“无参数构造函数”行为,同时保持Date的其他功能不变。例如,当将ISO 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();
}); 

答案 5 :(得分:1)

您可以将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);

答案 6 :(得分:1)

我刚刚编写了一个笑话测试,并且能够将new Date()global.Date = () => now进行存根

答案 7 :(得分:0)

您可以使用date-faker模拟新的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 :(得分:0)

我正在使用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 :(得分:0)

只需执行以下操作:

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)
}

对我有用

答案 10 :(得分:0)

就我而言,我必须在测试前模拟整个Date和'now'函数:

const mockedData = new Date('2020-11-26T00:00:00.000Z');

jest.spyOn(global, 'Date').mockImplementation(() => mockedData);

Date.now = () => 1606348800;

describe('test', () => {...})

答案 11 :(得分:-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());
  });

});