Future-Proof Your JavaScript Datetime Tests
How to fight flaky datetime tests — and win.
Working with dates and times is sometimes tricky, but you have gotten your Date code written and avoided all the pitfalls (read Edward Ezekiel’s article on how to avoid JavaScript Date pitfalls here!). Now it’s time to test your code. Let’s learn how to make working with dates and times in tests consistent and reliable with mocking.
What is Mocking?
“Mocking” in code testing is replacing the value of a class, function, or network request/response. It seems counterintuitive to want to “fake” how our code runs when we want to prove that it works, but mocking has many benefits:
- Simulates the behavior of the real method/class, giving better control of the execution of the code, and allowing for the easy creation of testing scenarios.
- Mocking prevents the behavior of dependencies from impacting the code under test. When you are writing tests, you want to make sure that one specific part of it works, not all of its dependencies. This creates isolation of test cases.
- Creates reliable, consistent test output that is not dependent on the internal workings of a dependency.
Remembering to mock network calls and dependent classes comes easy, but sometimes we forget to mock basic out-of-the-box JavaScript objects. Date in particular can lead to very inconsistent and unreliable test execution if not mocked properly.
Why Do Dates Need To Be Mocked?
Let’s suppose you have a date utility function that takes a date string and returns the difference in days between the current time and the given date string.
Your function works great, and to prove it, you write a simple Jest unit test to assert its correctness.
It works perfectly. The current date is May 1st, 2023 and your date utility function returns 3 days until May 4th, 2023. Push that code and get it to review!
The next day, you add some more code, and some more tests, and run your test suite again. You expect your tests to pass but you’ve got a failing test. Some tests failed in code you didn’t even touch!
Why is this test failing? The previous day was May 1st and so your test passed the check on your test string of May 4th. However now it’s May 2nd, so your days remaining utility function correctly returns 2. It gets worse! Tomorrow the correct result is 1!
You don’t want to have to update your date-related tests every day to account for the passage of time. Let’s make sure the test always passes and mock the date.
Set Up Your Date Mock
We’ll learn how to mock the current datetime in the Jasmine and Jest testing frameworks, two of the most popular unit testing frameworks for JavaScript and TypeScript applications. These mocks will change the result of new Date() and Date.now(), which returns the current datetime at the time of execution.
Jest
For Jest, we’ll call jest.useFakeTimers(). This replaces calls to Date and setTimeout with fake timers so that we can control the passage of time in our tests. Once the fake timers are installed, call jest.setSystemTime() and provide a Date. Until we remove these timers, new Date() and Date.now() will return whatever Date we set.
Now during our test, it will always be May 1st, 2023. You can provide a more specific date too, even giving a time and offset.
It’s always good to get in the habit of uninstalling or resetting any mock times at the exact same describe level you install it. This ensures your mock time does not bleed into other describe blocks and affect the results of other tests that may need a different date, or may not need to mock the date at all. We can do this with jest.useRealTimers().
You can also use jest.useFakeTimers() to simulate the passage of time for things like setTimeout() and setInterval(). See the Jest Object docs and Jest Timer Mocks docs for detailed API usage.
Jasmine
We can do the same thing with Jasmine. Use jasmine.clock().install() to install the mock timers. Then, run jasmine.clock().mockDate() and provide a Date.
Once again, make sure that you remove the mock timers with jasmine.clock().uninstall() at the same describelevel as the .install().
Like with Jest, you can use Jasmine’s Clock API to simulate the passage of time for setTimeout() and setInterval() as well See the Jasmine Clock docs for detailed API usage.
Now our tests will run perfectly today, tomorrow, and forever!
These Jest and Jasmine mocking mechanisms also work if you use JS date libraries like moment, Day.js, and date-fns.
Using stock JS Date is not required.
When Is Mocking Not Required?
These mocking mechanisms are not needed if the code being tested does not get the current date or time at runtime. For example, here’s a function that returns an expiration date for seven days after the given date string, using the date-fns date utility library.
Writing our test, we see that the result is always the same, no matter what day we run these tests.
The use of jest.setSystemTime() or jasmine.clock().mockDate() is not required here. getExpirationDate() is a is a pure function that always returns the same result when given the same input, and does not access the current date for its calculation.
Conclusion
Jest and Jasmine offer sophisticated out-of-the-box solutions for mocking the current time so your tests can run in a predictable manner. Use them the next time you find yourself fighting with flaky datetime tests to give your test suite a reliability boost!