ReactTDD.com

Controlling time within unit tests

When you’re building components that contain date logic, it’s always best to fix the date (and maybe time) within your tests.

The reason for this is that not doing this can lead to intermittent test failures. Let’s take a look at some examples.

const MyComponent = () => (
  <p>Today is {new Date().toDateString()}</p>
);

it("renders the date", () => {
  render(<MyComponent />);
  expect(document.body.textContent).toContain(
    new Date().toDateString()
  );
});

This example will fail if you run the text exactly at midnight and the date changes between the rendering of the component and the invocation of the expectation. The reason being the two different calls to new Date(): your test depends on the real progression of time.

Okay, so that’s hardly ever going to happen. What about this?

const MyComponent = ({ dbTime }) => (
  <p>Data saved at {new Date(dbTime).toTimeString()}</p>
);

it("renders the time", () => {
  render(<MyComponent dbTime="2022-08-25T15:36:57.191Z" />);
  expect(document.body.textContent).toContain(
    "15:36:57"
  )
});

Whether this test passes depends on your timezone: for those in UTC, it will pass: but runs your tests anywhere else (perhaps on CI) and the test will fail. Or wait until daylight savings time strikes, and now your test suddenly starts failing.

Yes, there is error with your source code that you probably want to do something about. But the fact that the test sometimes passes and sometimes doesn’t (in other words it intermittently fails) is also an issue that you should do something about.

A simple way to do this is to avoid calling new Date() in your components, but only do that at the edge of your system and pass the value in as a prop. Then, your test can pass in a fixed date or time to each test:

const MyComponent = ({ date }) => (
  <p>Today is {date.toDateString()}</p>
);

it("renders the date", () => {
  const now = new Date();
  render(<MyComponent date={now }/>);
  expect(document.body.textContent).toContain(
    now.toDateString()
  );
});

Or perhaps even better, use an example of a date value in your test:

const MyComponent = ({ date }) => (
  <p>Today is {date.toDateString()}</p>
);

it("renders the date", () => {
  const now = new Date("2022-08-25");
  render(<MyComponent date={now }/>);
  expect(document.body.textContent).toContain(
    "Thu Aug 25 2022"
  );
});

That still doesn’t protect you in some situations: when you’re calling timezone specific functions, or when you are testing components that you didn’t write and don’t control where you can’t modify the source.

For Jest programmers, you can use Jest’s jest.setSystemTime function, which allows your application code to use the standard Date API. For Sinon users, you have the same thing as clock.setSystemTime.

const MyComponent = () => (
  <p>Today is {new Date().toDateString()}</p>
);

it("renders the date", () => {
  jest.setSystemTime(new Date("2022-08-25"));
  render(<MyComponent />);
  expect(document.body.textContent).toContain(
    "Thu Aug 25 2022"
  );
});

This is sometimes known as “freezing” time, where you can move the clock back and forward as you wish.

Complex time manipulation within a single test, where you might be setting the clock multiple times, is more usual to do within backend tests. Backend APIs are often responsible for initiating a series of actions in a specific order, and sometimes there are specific validations that come into play (like “don’t let the customer place an order on Saturday mornings”).

Speaking of the backend, another trick you can use is an “auto-ticking clock”, where is like setting the system time but where the clock automatically advances by a period of time (like a second) every time the current time is requested. This can be really useful for reasoning about the order in which actions occur.

If you have any other ideas and suggestions for how you handle time, please drop me a note! I’d love to hear about them.

— Written by Daniel Irvine on August 25, 2022.