A Matter of Time

Time-dependent testing in Jest

--

Photo by Sonja Langford on Unsplash

Testing JavaScript code that has time-dependencies can be problematic since you want to remove any actual delays from your tests and predictably control “when” the tests are run. Jest provides some functionality to handle this but it must be used correctly. Interacting with the system to obtain the current date/time is also challenging for testing purposes but becomes simpler if this access is encapsulated within its own module. The techniques shown here enable you to write fast tests that predictably control time.

The sample code for this article is available at https://github.com/kbwood/testtime.

Simple timeouts

Suppose you have a popup message component that briefly displays some text on the page.

Photo by Nigel Tadyanehondo on Unsplash

The component simply accepts the message text and a flag to control its visibility, whereas the corresponding container manages that visibility flag. When the message changes the component becomes visible and after a certain time it disappears again. You can use a setTimeout for this, with the appropriate delay, as shown in the container code below.

To test the delayed code you use the fake timers provided by Jest, which allow you to take control of the clock for setTimeout and setInterval calls (amongst others). Before you run any of your tests you need to turn the fake timers on with a call to jest.useFakeTimers. Make sure that at the end of your tests you restore the status quo by calling jest.useRealTimers.

Within your test you initialise the container and then trigger the timeout processing by changing the text property. You control the internal clock through the jest.advanceTimersByTime function to adjust the time by a specified number of milliseconds. In this case you check that the message is immediately visible, then jump to just before the timeout (MESSAGE_DELAY -1) to check that it is still visible. Then add one more millisecond to verify that it disappears.

If you’re not concerned with the actual length of the timeout you can simply run all outstanding timers by calling jest.runAllTimers and then check the results. Note that this function runs any existing timeouts or intervals that are set, and also calls any further timers that these may create. In this test (an alternative to the one above) you again check that the message is immediately visible upon changing the text and then is hidden after the timer has expired.

Date objects

Sometimes you need to get the current date and time for display or logging.

Photo by rawpixel on Unsplash

Although you can sprinkle calls to Date throughout your code, it simplifies matters to centralise that process, making it easier to test the code that depends on it through mocking.

You can test this function in isolation by mocking the calls to the Date constructor through jest.spyOn. Don’t forget to restore the original Date at the end of your tests by calling mockRestore on your mocked function. You need to keep a reference to the real Date so that you can create the appropriate objects even though you’ve mocked the constructor. Use mockImplementationOnce to control how your mock responds to the next call to it.

Repeating intervals

To make use of this new getDateTime function, and demonstrate the testing of a setInterval call, you can create a clock component that updates every second to display the current time.

Photo by Thomas Bormans on Unsplash

As before, the component just accepts the time to be shown and renders it, whereas the corresponding container controls the regular updating of that time. Once mounted, the container starts a periodic callback that retrieves the current date/time and sets it for the underlying component. Ensure that you clear the timer when the component is unmounted to avoid a memory leak.

Testing is similar to the setTimeout example. However, you mock out the getDateTime call to gain control over the values that it returns (using jest.mock automatically reverts at the end of the test). Although you control the timer through the advanceTimersByTime call, that doesn’t update the actual system clock, so you need to increment the current time in parallel. To facilitate this process you mock the getDateTime call to return a variable containing a given Date and then manipulate that value as necessary.

As in the first example, if you’re not concerned with the actual time period you can run all the timers and check their results. However, since you are using setInterval, each timer expiry effectively creates a new timer for the next period. So you can’t call the jest.runAllTimers function used previously as that would run forever. Instead you must call jest.runOnlyPendingTimers, which just runs those timers that have already been invoked, but no new ones that they may create.

Animation frames

If you are performing UI updates for scrolling or animations you might be using requestAnimationFrame to schedule these changes between repaints. That’s great for front-end performance, but harder for testing since the updates happen asynchronously.

The simplest way to deal with these calls is to make them synchronous for the purposes of your tests. You mock requestAnimationFrame to immediately invoke the callback function passed to it. Don’t forget to restore its original functionality at the end of your tests.

Conclusion

Writing tests for time-dependent code that are fast and predictable can be a challenge. Fortunately Jest provides tools for dealing with these calls — you take over the timer clock and control exactly how it ticks. Along with ways to encapsulate Date for better testing control, you are now a master of time.

--

--