【问题标题】:How do I set a mock date in Jest?如何在 Jest 中设置模拟日期?
【发布时间】:2015-06-25 12:15:26
【问题描述】:

我正在使用 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 现在有内置的日期模拟功能?

标签: javascript jestjs momentjs


【解决方案1】:

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

这行得通:

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

【讨论】:

    【解决方案2】:

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

    目标是在组件渲染期间使用固定日期模拟 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();
        });
    });
    
    
    

    【讨论】:

    • 解释你的答案。只放代码不是好方法
    • 感谢您的建议。用 cmets 更新。
    【解决方案4】:

    从 Jest 26 开始,这可以使用“现代”假计时器来实现,而无需安装任何 3rd 方模块:https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers

    jest
      .useFakeTimers()
      .setSystemTime(new Date('2020-01-01').getTime());
    

    如果您希望假计时器在所有测试中处于活动状态,您可以在配置中设置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()在你完成假计时器之后。
    • 这是最干净的解决方案。顺便说一句,您也可以给 setSystemTime 一个 Date 对象,无需转换为纪元时间。
    【解决方案5】:

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

    【讨论】:

    • 谢谢,这只是解决了我遇到的问题。
    【解决方案6】:

    接受的答案效果很好 -

    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)

    【讨论】:

      【解决方案7】:

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

      如果您在项目中使用了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 只是满足打字稿的代理变量。

      我试过的完整代码:

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

      它对我有用。

      【讨论】:

        【解决方案8】:

        稍微改进一下@pranava-s-balugari 的响应

        1. 不影响new Date(something)
        2. 模拟日期可以更改。
        3. 它也适用于 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'));
          })
        })
        

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

            在我的例子中,我必须在测试之前模拟整个 Date 和 'now' 函数:

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

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

            Date.now = () =&gt; 1606348800;

            describe('test', () =&gt; {...})

            【讨论】:

              【解决方案11】:

              这对我有用:

              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
              

              【讨论】:

                【解决方案12】:

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

                一次性模拟

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

                【讨论】:

                  【解决方案13】:

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

                  【讨论】:

                    【解决方案14】:

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

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

                    【讨论】:

                    • 这是一个很好的方法。我结合了几个答案并将其放在我的一个测试文件的顶部:jest.spyOn(global.Date, 'now').mockImplementation(() =&gt; 1487076708000);
                    【解决方案15】:

                    我只是想在这里插话,因为如果您只想在特定套件中模拟 Date 对象,没有答案可以解决这个问题。

                    您可以使用每个套件的 setup 和 teardown 方法来模拟它,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;
                    });
                    

                    希望这会有所帮助!

                    【讨论】:

                    • 天啊为什么 Date.UTC 使用基于 0 的月份?我以为我要疯了,因为整个时间都休息了一个月。 new Date(Date.UTC(2020, 0, 8)).valueOf() --> 2020 年 1 月 8 日 new Date(Date.UTC(2020, 1, 8)).valueOf() --> 2020 年 2 月 8 日然后仅几个月,而不是几天或几年developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
                    【解决方案16】:

                    由于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(() =&gt; new Date(Date.UTC(2017, 0, 1)).valueOf());
                    • 或者更漂亮一点:Date.now = jest.fn(() =&gt; +new Date('2017-01-01');
                    • 或:Date.now = jest.fn(() =&gt; Date.parse('2017-02-14))
                    • 这会正确模拟Date 的所有用途吗?喜欢new Date()
                    • @EliasZamaria 没有。 This answer 确实涵盖了该用例。
                    【解决方案17】:

                    我想使用 Manual Mocks,所以它可以在所有测试中使用。

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

                    【讨论】:

                      【解决方案18】:

                      我想提供一些替代方法。

                      如果您需要存根 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 中查看它的实际应用
                      【解决方案19】:

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

                      【讨论】:

                      • 你的情况是什么?
                      【解决方案20】:

                      所有仅基于 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;
                      

                      【讨论】:

                        【解决方案21】:

                        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(() =&gt; new Date('1990-03-30T09:00:00')); 但它会阻止Date 上的静态方法工作。
                        猜你喜欢
                        • 2017-12-28
                        • 1970-01-01
                        • 2014-07-22
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-09-04
                        • 2019-12-31
                        • 2020-10-02
                        相关资源
                        最近更新 更多