Mocking setTimeout with Jest
Tags: javascript, testing, jest, webdev
Published: 2022-09-10 11:58:54

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:
{% gist https://gist.github.com/marekrozmus/0dd12e648ca50f9e9a9b609427349a2e.js %}

The whole code can be found in this GitHub repository: [https://github.com/marekrozmus/blog_mocking_settimeout_with_jest](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 until `setTimeout` 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:

![Exceeded timeout of 5000 ms for a test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sww0nandbr8ddkm7odi0.png)

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](https://jestjs.io/docs/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()`.
{% gist https://gist.github.com/marekrozmus/c3aef40571bae9adda6beafb07ae6e46.js %}

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.
{% gist https://gist.github.com/marekrozmus/932adac2aff29bc61d04e349fe25ed4c.js %}

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.

![Not wrapped in act](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1ovwzkhgsyu3pmaql2b.png)

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`.
{% gist https://gist.github.com/marekrozmus/92975e6f0b961e39c2ea1e163e95a144.js %}

## 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:

{% gist https://gist.github.com/marekrozmus/70499ed6de33d9540488ad921a71d26b.js %}

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](https://github.com/testing-library/user-event/releases/tag/v14.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`.

{% gist https://gist.github.com/marekrozmus/d0ff7e1e10378f23d4ddd592e059d9e1.js %}

Other thing is that we need to [setup](https://testing-library.com/docs/user-event/setup) the `userEvent` instance to properly behave with Jest’s fake timers. We need to set it up as following:

{% gist https://gist.github.com/marekrozmus/34202cf020e0e12df32d63ee9e5b5bb0.js %}

## 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 :)

[![Buy me a coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g1tifscty74ayzn0vxpl.png)](https://www.buymeacoffee.com/froostrat)

Check also my other posts about unit testing:

* [Mocking window object with Jest](https://marek-rozmus.medium.com/mocking-window-object-d316050ae7a5?source=friends_link&sk=9d8ddce0b5bc37cdab0c721cbf617c6e)

* [When to use mockClear, mockReset and mockRestore with mock and spyOn.](https://marek-rozmus.medium.com/jest-mock-and-spy-mockclear-vs-mockreset-vs-mockrestore-f52395581950?source=friends_link&sk=0958f2572071d35d8b3061491040f3ed)