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 usingjest-environment-jsdom-sixteen
as the testing environment.
em/package.json
Line 120 in5baf45d
"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 ourpackage.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.
Top comments (2)
Awesome post! You have saved me!
Oh great! This wasted SO MUCH of my time, so I'm happy to save other people some of that hassle!