Mocking setTimeout with Jest
The title of this post may be a little misleading as we won’t write any mocks but we need to use some functionality that Jest provides. Let’s start!
Here is an example of a simple component:
The whole code can be found in this GitHub repository: https://github.com/marekrozmus/blog_mocking_settimeout_with_jest
It is component with three buttons and every click handler contains a setTimeout
usage.
Writing the (incorrect) test may result in:
- very fast test run but with incorrect results— the test skips all the
setTimeout
logic, it just doesn’t wait untilsetTimout
finishes - it will take long time (but 5 seconds at most )— the test is wating for
setTimeout
to finish but in our case it is longer (10 seconds) than default test’s duration allowed in Jest. We will get a following error:
We could increase timeout for unit tests but it is not a good practice. Usually when test exceeds timeout, it means that it is broken. Most probably async
/ await
is missing somewhere or some API calls are missing mocks.
What to do with setTimeout
?
Jest has a built in mechanism to handle such situation — the timer mocks. When we enable them we can “fast-forward time” inside the test.
Use fake timers
We need to place the testing code between jest.useFakeTimers()
and jest.useRealTimers()
.
The first one is setting up fake timers and the second one restores the original JS behaviour.
Example 1 — setTimeout calls some API
The simplest case. The setTimeout
callback is calling some function.
We just tell the Jest that all timers should complete. After that we can test if the callback logic was executed.
Example 2 — setTimeout updates component’s state
In this case we can encounter the “not wrapped in act(…)” error.
This is because the setTimeout
callback is updating the component’s state. The Jest’s method that complete the timers needs to be wrapped in act
.
Example 3 — setTimeout throws an exception
The callback method is throwing an exception after 10 seconds. In this case we need to pass a function to expect
as following:
In above example first we expect
that after 9 seconds there won’t be an exception. We expect it after the timer’s finish.
fireEvent vs userEvent
There is one more thing worth to mention. The main difference in writing test when using fireEvent
or userEvent
to click the button.
When using fireEvent
we get synchronous call to click. No additional setup is needed to test setTimeout
with fake timers.
With userEvent
it is different. Since version 14.0.0 the APIs always return a Promise. Because of that we need to make the whole test async
and await on clicking with userEvent
.
Other thing is that we need to setup the userEvent
instance to properly behave with Jest’s fake timers. We need to set it up as following:
Conclusion
That’s it. Thank you for reading and leave me a comment if you have some question or proposal for making this post better.
Check also my other posts — happy testing :)
Check also my other posts about unit testing: