Problems with using useFakeTimers('modern') in a create-react-app (CRA) project with Jest 26 and Lodash's debounce function
Tags: jest, javascript, react, testing
Published: 2021-11-22 21:24:40

I spent the best part of a day (after meetings etc) working why something that seems so simple in the Jest documentation wasn't working for me. I've written up some notes to hopefully help anyone else who is having the same issue.

I was trying to test a component that used Lodash's [debounce](https://lodash.com/docs/4.17.15#debounce) function without having to slow the tests down by waiting for the debounce timer to be hit each time.

Our CRA (Create React App) project at work was using Jest 26 and so I had been [following the documentation](https://jestjs.io/docs/26.x/timer-mocks) and trying to use something like this to skip the debounce timer:

```javascript
// Use the new fake timers approach from Jest 26:
jest.useFakeTimers('modern');

// Type into the search input to trigger our autocomplete/
// search suggestions:
const input = screen.getByLabelText(/search query/i);
userEvent.type(input, 'example product name');

// Skip the debounce timer to make sure the search
// suggestions appear without any delay. We have to
// use 'act' here, see https://egghead.io/lessons/jest-fix-the-not-wrapped-in-act-warning-with-jest-fake-timers.
act(() => {
  jest.runOnlyPendingTimers();
});

// Make sure we see what we want to see:
expect(screen.getByText('Example product name')).toBeInTheDocument();
```

`jest.useFakeTimers('modern')` was [added in Jest 26](https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers) and I had double-checked our `package-lock.json` to make sure that was what we were using, so I was surprised that this approach didn't work for me. I was getting an error message that I couldn't find any Google results for (`TypeError: Cannot read properties of undefined (reading 'useFakeTimers')`), and being new to Jest and CRA, I assumed this was my fault. I kept trying slightly different approaches, but never got very far. I was perplexed as to why every example of `jest.useFakeTimers('modern')` online seemed so simple, and yet my tests were all still failing with odd errors.

When I am debugging an issue in something as widely used as Lodash or Jest or Create React App one technique I like to use is to search Github for references to the thing I am struggling with. It's useful to see code, pull requests, and issues that give examples of how other people are using the thing that I am trying to use.

I spent quite a lot of time reading through the ideas on this long-running issue: [calling runAllTimers after using Lodash's `_.debounce` results in an infinite recursion error](https://github.com/facebook/jest/issues/3465). That gave me the tip to switch from `jest.runAllTimers()` to `jest.runOnlyPendingTimers()`, but I was still getting the `TypeError: Cannot read properties of undefined (reading 'useFakeTimers')` error message.

I kept looking through Github issues and PRs to try and work out what my local application was missing, and why the documentation examples didn't work for me. Eventually, I [found this issue](https://github.com/cybersemics/em/issues/919) and its [associated pull request](https://github.com/cybersemics/em/pull/927) where a contributor discovered why their use of `jest.useFakeTimers('modern')` was failing:

> I finally figured out why `useFakeTimers('modern')` is not working. Even though we upgraded the react-scripts which has implementation for modern implementation of fake timer, we are still explicitly using `jest-environment-jsdom-sixteen` as the testing environment.
>
>> `em/package.json`
>> Line 120 in `5baf45d`
>> `"test": "react-scripts test --env=jsdom-sixteen",`
>
> It still does not pass modern implementation of fake timer to its environment. Jest 26 ships with Jsdom 16 by default. So we don't need to pass this environment here. I tested the Lodash's debounce with upgraded react-scripts and Jest and it's working with `useFakeTimers('modern')`.

We had the example same issue on my project. `react-scripts` had been updated to a version which uses Jest >26, but the `package.json` was still telling the `test` script to use a Jest environment provided by the deprecated npm package [`jest-environment-jsdom-sixteen`](https://www.npmjs.com/package/jest-environment-jsdom-sixteen).

I did some digging and it looks like `testing-library/dom-testing-library` recommended using `jest-environment-jsdom-sixteen` in its [release notes for v7.0.0](https://github.com/testing-library/dom-testing-library/releases/tag/v7.0.0) because CRA was using an older version of Jest that provided an older version of jsdom, and that older jsdom was missing support for a few modern web features. Eventually, CRA was updated to use the newer version of Jest, and this made using `jest-environment-jsdom-sixteen` unnecessary – and in my case actually harmful as it prevented me from using the new `useFakeTimers('modern')` functionality. Once I removed the `--env=jsdom-sixteen` line from the `test` script in `package.json` everything started working as I expected. 🎉

## So, what did I learn?

* When you're using something popular like Lodash, Jest, or CRA it's useful to search Github to see examples of working code, and you can gain a _lot_ of additional information and context from reading the discussions and pull requests
* When you're using a tool you're not super familiar with (like me and Jest) don't forget about things defined outside of your code that could still affect behaviour, like environmental variables, or in this case the command line interface argument that we were passing to Jest in the `scripts` section of our `package.json` file
* Don't be too quick to assign yourself blame! I had seen that `TypeError: Cannot read properties of undefined (reading 'useFakeTimers')` message numerous times and each time assumed I was doing something wrong, despite my code matching the documentation exactly. This lead to me spending time doubting myself and trying a load of different approaches, instead of instead focussing on a single approach and removing non-code variable factors until the same code from the documentation also worked in my local environment.