ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Jest 中设置模拟日期?

我正在使用 moment.js 在我的 React 组件的帮助文件中执行大部分日期逻辑,但我无法弄清楚如何在 Jest a la sinon.useFakeTimers() 中模拟日期。

Jest 文档只讨论了 setTimeoutsetInterval 等计时器功能,但没有帮助设置日期,然后检查我的日期功能是否符合它们的预期。

这是我的一些 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 但它似乎有点不稳定,我想将日期设置为测试的固定时间。

关于如何实现的任何想法?

你能选择一个不同的答案吗,因为 jest 现在有内置的日期模拟?

I
Ian Grainger

从 Jest 26 开始,这可以使用“现代”假计时器来实现,而无需安装任何 3rd 方模块: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 中。


谢谢,我认为这应该是这个问题的解决方案。
这绝对是解决问题的最简单方法
另外值得注意的是,.useFakeTimers('modern') 位可以从全局配置文件(如 setupTests.js)中调用。因此,一旦这是默认选项,就可以轻松删除它。来自同一链接:In Jest 27 we will swap the default to the new "modern"
完成假计时器后,您可能想要jest.useRealTimers()
仅当我在测试设置中调用 jest.setSystemTime() 时,此解决方案才对我有效;如果我在我的测试套件的 beforeAll 中调用它,它会被忽略。检查我为测试此 github.com/dariospadoni/jestFakeTimersMock/blob/main/src/… 而创建的存储库
s
stereodenis

由于 momentjs 在内部使用 Date,您只需覆盖 Date.now 函数以始终返回相同的时刻。

Date.now = jest.fn(() => 1487076708000) //14.02.2017

或者

Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())

这是设置将返回的实际日期的更漂亮的方法:Date.now = jest.fn(() => new Date(Date.UTC(2017, 0, 1)).valueOf());
或者更漂亮一点:Date.now = jest.fn(() => +new Date('2017-01-01');
或:Date.now = jest.fn(() => Date.parse('2017-02-14))
这会正确模拟 Date 的所有用途吗?像new Date()
@EliasZamaria 没有。 This answer 确实涵盖了该用例。
T
Tim Santeford

对于快速而肮脏的解决方案,使用 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();
});

很好的解决方案;没有依赖关系并保持可重置使其易于应用于单个测试。
不需要 dateNowSpy 变量,根据 jestjs.io/docs/en/mock-function-api.html#mockfnmockrestoremockReset() 是多余的。在 afterAll 中,您可以简单地执行 Date.now.mockRestore()
这很好,所以你不需要任何额外的库。但这只有在您使用静态 Date 方法(不多)时才真正起作用
@Jimmy Date.now.mockRestore(); 给出了一个 Property 'mockRestore' does not exist on type '() => number' 错误
@Marco 它应该是 jest.spyOn(Date, "now").mockRestore();
e
eadmundo

MockDate 可用于开玩笑测试以更改 new Date() 返回的内容:

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();

效果很好,因为我使用了 Date 的其他功能,例如 valueOf()
同样,我需要模拟日期,但也有可用的 Date.parse,这非常有效!我以前这样做过:dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => new Date('1990-03-30T09:00:00'));,但它会阻止 Date 上的静态方法工作。
R
RobotEyes

对于那些想要模拟 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()
});

谢谢,这只是解决了我遇到的问题。
a
atool

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():关闭模拟系统。


你是什么情况?
m
mattdlockyer

以下是针对不同用例的一些可读方法。我更喜欢使用间谍而不是保存对原始对象的引用,这些引用可能会在其他一些代码中被意外覆盖。

一次性嘲讽

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

C
ClementWalter

由于某些软件包(例如 moment.js)使用 new Date() 而不是所有仅基于模拟 Date.now() 的答案都不会在任何地方都有效。

在这种情况下,基于 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;

D
Dawood Valeed

这就是我如何模拟我的 Date.now() 方法以将年份设置为 2010 年进行测试

jest
  .spyOn(global.Date, 'now')
  .mockImplementationOnce(() => new Date(`2010`).valueOf());

这是一个很好的方法。我结合了几个答案并将其放在我的一个测试文件的顶部:jest.spyOn(global.Date, 'now').mockImplementation(() => 1487076708000);
D
Duc Trung Mai

这对我有用:

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

很好,但不适合 Typescript 而不应用 @ts-ignore
我正在使用 NestJS,上面的代码对我来说很好,不需要 @ts-ignore
b
bigpotato

我正在使用 moment + moment-timezone ,但这些都不适合我。

这有效:

jest.mock('moment', () => {
  const moment = jest.requireActual('moment');
  moment.now = () => +new Date('2022-01-18T12:33:37.000Z');
  return moment;
});


D
David

我想提供一些替代方法。

如果您需要存根 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


对于第一个示例,我得到 TypeError: moment.mockReturnValue is not a function
jest.mock("moment") 与您的导入语句处于同一级别吗?否则,欢迎您在 this project 中查看它的实际效果
c
codelegant

我想使用手动模拟,所以它可以在所有测试中使用。

// <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

R
Ridd

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

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

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

Date.now = () => 1606348800;

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


p
pykiss

稍微改进@pranava-s-balugari 响应

它不会影响新的日期(某事)模拟的日期可以更改。它也适用于 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'));
  })
})

D
Dmitry Grinko

接受的答案效果很好 -

Date.now = jest.fn().mockReturnValue(new Date('2021-08-29T18:16:19+00:00'));

但是如果我们想在管道中运行单元测试,我们必须确保我们使用相同的时区。为此,我们还必须模拟时区 -

jest.config.js

process.env.TZ = 'GMT';

module.exports = {
 ...
};

另请参阅:the full list of timezones (column TZ database name)


M
MoMo

我只是想在这里插话,因为如果您只想在特定套件中模拟 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;
});

希望这可以帮助!


OMG 为什么 Date.UTC 使用基于 0 的月份?我以为我要疯了,因为整个时间都休息了一个月。新日期(Date.UTC(2020, 0, 8)).valueOf() --> 2020 年 1 月 8 日新日期(Date.UTC(2020, 1, 8)).valueOf() --> 2020 年 2 月 8 日,然后仅持续几个月,而不是几天或几年developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
M
MatGar

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

B
Barnesy

我发现最好的方法就是用你正在使用的任何功能覆盖原型。

Date.prototype.getTimezoneOffset = function () {
   return 456;
};

Date.prototype.getTime = function () {
      return 123456;
};

k
kartik tyagi

以下测试存根 Date 在测试生命周期中返回一个常量。

如果您在项目中使用了 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);
  });
});

它对我有用。


k
ksav

目标是用固定日期模拟 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();
    });
});



解释你的答案。只放代码不是好方法
谢谢你的建议。更新了评论。