Mocking the system clock is extremely important when you are dealing with testing. It's important so you can deal with time-based tests - say a test that deals with ensuring that a certain feature is only available during working hours for, instance.
Let's have a look at an even simpler use case. You want a function that tells you if a date is in the future.
function isInTheFuture(date) {
return new Date().getTime() - date.getTime() < 0
}
A very simple way to deal with this unit test would be to test it with a date long passed, or far away in the future. This way the test will be green (for the next 30 years at least).
it('returns true when the date is in the future', () => {
const date = new Date('2050-01-01')
expect(isInTheFuture(date)).toBe(true)
})
Another way to do this is to extract the current date as an argument to your function so you can actually test it:
function isInTheFuture(currentDate, dateToTest) {
return currentDate.getTime() - dateToTest.getTime() < 0
}
it('compares two dates', () => {
const currentDate = new Date('2019-01-01')
const dateInTheFuture = new Date('2020-01-01')
expect(isInTheFuture(currentDate, dateInTheFuture)).toBe(true)
})
This way, it is very easy to unit test, but it is not as easy to understand or maintain.
Another "common" way of doing this would be to manually monkey patch the date object. But that's error-prone, and it's better to leave that responsibility to someone else.
Fortunately, in version 26, Jest introduced a new and more powerful time mock. They enabled the usage of @sinonjs/fake-timers, even though, for now, the feature is still a bit hidden.
To use the new mock system, you need to pass the "modern" argument to the jest.useFakeTimers function. This system will allow you not only to mock timers as you already could but also to mock the system clock.
Give the first implementation, you would be able to write tests that looks like this:
describe('Time based test with mock', () => {
beforeAll(() => {
jest.useFakeTimers('modern')
jest.setSystemTime(new Date('2017-01-01'))
})
afterAll(() => {
jest.useRealTimers()
})
it('returns true when the date is in the future', () => {
const date = new Date('2019-01-01')
expect(isInTheFuture(date)).toBe(true)
})
})
This way, the test will be green, but will also be stable in time. This new mock system will become the default in Jest 27. Until then, we'll have to add that extra parameter to the useFakeTimers call.
Top comments (5)
Thanks a lot!
I just tested and it does not seem to work in my case unless I call setSystemTime in the test setup file. I created a repo to test the problem I am facing github.com/dariospadoni/jestFakeTi... and here is my question on SO stackoverflow.com/questions/663330...
Hello! Thanks for commenting!
It's been explained well in the SO thread, but basically the problem here is that the data is initialised when you execute the import statement, so the only way for the date to be mocked is actually to mock it before the file is imported (which is why it works when you mock it in the setup file).
If the date was created in your function instead of at the top level of the code, the mock would work.
Does that make it clearer?
Yes, it makes totally sense, thanks Quentin.
And thanks again for your post!
Thanks so much for this tip. Exactly what I needed to get unblocked during a Jest upgrade. :-)