How can I mock a call to a constructor once in jest

Viewed 324

As the title says, I'm having some difficulty mocking a call to new Date() constructor in Jest. I'm mocking this call to ensure my tests do not fail, by returning a specific date time. However, as the date constructor is called twice in the function I'm testing, the second call also has its implementation mocked too, this second call should not be mocked.

I've provided an example below for context, it's a little function that returns the number of seconds until tomorrow, hence the requirement to mock a consistent now time.

Any assistance would be a great help, I've exhausted searches through Stack Overflow and the Jest docs.

Thanks in advance.

Function

function getSecondsToTomorrow() {
  // This call needs to be mocked to always return the same date time
  const now = new Date();
  
  // This call should not be mocked
  const tomorrow = new Date(
    now.getFullYear(), 
    now.getMonth(), 
    now.getDate() + 1,
    );

  return Math.round( (tomorrow - now) / 1000);
}

Tests

describe("getSecondsToTomorrow()", () => {
  let result, mockDate, now;

  beforeAll(() => {
    now = new Date(2020, 7, 3, 23, 0, 0);
    mockDate = jest.spyOn(global, "Date").mockImplementation(() => now);
    result = getSecondsToTomorrow();
  });


  afterAll(() => {
    mockDate.mockRestore();
  });

  it("should return the number of seconds until tomorrow, from the time the function was called", () => {
    // We mock the date to be 2300, so the return should be 1 hour (3600 seconds)
    expect(result).toBe(3600);
  });
});

Update

Although I still haven't found a specific answer to my question, I have found a workaround. Namely using Jest's mockImplementationOnce method, I can mock a datetime on the first call to new Date() and pass through any arguments on the second call as a default mockImplementation.

describe("getSecondsToTomorrow()", () => {
  let result, mockDate, now, dateClone;

  function setUpDateMock(now) {
    return jest
      .spyOn(global, "Date")
      .mockImplementation((...args) => new dateClone(...args))
      .mockImplementationOnce(() => now)
  }

  beforeAll(() => {
    dateClone = Date;
    now = new Date(2020, 7, 3, 23, 0, 0);
    mockDate = setUpDateMock(now);
    result = getSecondsToTomorrow();
  });

  afterAll(() => {
    mockDate.mockRestore();
  });

  it("should return the number of seconds until tomorrow, from the time the function was called", () => {
    expect(result).toBe(3600);
  });
});
1 Answers

IDK, but the way with cloned Date doesn't work for me. On the second call to new Date() debugger shows that the returned value is mockConstructor {}. So, my workaround is to restore Date:

const dateSpy = jest.spyOn(global, 'Date')
      .mockImplementationOnce(function () {
        dateSpy.mockRestore();

        return myMockedDate;
      });
Related