Some Tips and Tricks for Testing React Code

I’ve had the opportunity to review a lot of frontend code recently, and I’ve noticed people making a lot of the same mistakes when testing React code that I used to make, so I figured I should make a post about my learnings so that everyone can benefit.

I found a blog post that covers a lot of what I was going to say and more, so I’ll link to that first.

One thing that I see quite often is using *ByTestId and adding test IDs to elements. I think this should be the absolute last resort. The *ByRole queries evaluate the DOM the way a user sees it from an accessibility POV. If you have a div with the role of a button, it will show up with the role button in this query so it can test how the UI actually functions for a user.

I think one big issue people might be having is navigating the DOM and figuring out how to pluck the right element and use the appropriate query. For this, I mush highly recommend using the screen import from @testing-library/react. This provides some amazing tools that really simplify many things for you.

The most useful tool here is screen.logTestingPlaygroundURL(). Just plop this in your test before you make any assertion, and it will output a URL to the console that takes you to a page that shows the UI in a special testing view where you can click on each element and get exactly the query you need to use to get to it.

Here is an example URL from the discussions MFE: link

Yes, that is an extremely long URL because it’s encoding the entire UI into the URL. If you’re testing a smaller component, it will be much shorter.

So here is what you can do:

  • Make a bare test with all the setup of the UI.
  • Log the screen before forcefully failing the test.
  • Figure out the query you need for your assertions and add it.
  • Fire events etc. that will change the UI, log the screen and fail the test again.
  • Repeat.

You can also use screen.debug() which will output the rendered document to the console. You can even pass it the result of a get query, and it will output that.

Another thing I can strongly recommend is using functions in tests that reduce repetition. If each test is setting up the React component and passing roughly similar arguments, you can move that to a function that does all the setup and takes only the parameters that change.

It’s also worthwhile to use a factory library like rosie to create factories that can generate an API response. For example, here you can see a factory that generates discussion threads. Here it will automatically increment the ID, create a title that includes the topic ID, alternate between “discussion” and “question”, pin the first three results, mark every third one as closed etc. You can then use axios-mock-adapter to return these API responses. After that, nothing else needs to be mocked. This is a one-time effort that over time can help a lot.

A few other things I’ve found useful for testing:

  1. You can pass parameters to jest when running npm run test as npm run test -- --help.
  2. You can automatically run test each time code changes with npm run test -- --watch.
  3. You can test just the part of your app you want by passing the path to the test file as : npm run test -- src/component/MyTest.test.jsx
  4. You can further refine your test by adding a -t "test button" to only run test that have “test button” in their name.
  5. You can disable coverage while doing quick test iteration using --coverage=no.
  6. You can focus on re-running failing tests using -f or --onlyFailures.
  7. You can use --verbose to show all your tests in a hierarchy with a checkmark next to it.
  8. You can use --silent to hide all output except coverage (if enabled) and just show the result.
  9. You can use both silent and verbose together to show the hierarchy and nothing else.
  10. Normally, the coverage output will show the coverage from the whole code, and if you limit testing to a component or part of the code it will annoyingly show missing coverage from the rest of the code. If you’re aiming to improve coverage in a part of the code, you can limit coverage reporting to that area by passing `–collectCoverageFrom ‘src/my-component/*’.

Thank you for this @kshitij!
I learned a lot from you in the last reviews!

One other thing to add is lint fixes, most of the time we can use --fix option with eslint to fix minor issues, I feel we should even add a pre-commit hook for the same.

1 Like

Thanks @kshitij! It’s very valuable information :raised_hands: