Problems with using useFakeTimers('modern') in a create-react-app (CRA) project with Jest 26 and Lodash's debounce function

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 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 and trying to use something like this to skip the debounce timer:

// 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 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. 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 and its associated pull request 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.

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 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.

28