After creating a React app, testing and understanding why your tests fail are vital. When debugging, you’re trying to identify and solve the error. When a test fails, it tells you what has gone wrong and why—in short, testing and debugging work in tandem. Debugging is the process of finding and fixing bugs. As a developer, it’s good to test your code. In this post, I’ll discuss using React Testing Library to debug common issues and suggest relevant fixes.
Simple Debugging With Default Test Runner
Use the default Jest test runner to run tests from the React Testing Library with a React application and run this command to see the list of components on the page:
jest -p <path to your react application>/bin/<name-of-your-react-app> --coverage -t debug.test.js .
For example, if you’re running a local development version of your application, you can open a new terminal window and use cd to change directories into the location of your React app; then, type react-scripts start, and finally, type
jest -p ./src/bin/<name> --coverage -t debug.test.js .
Once you’ve opened the coverage viewer, select the “components” tab to ensure that Jest has found all your React components. If you see a list of components, you know that Jest is running and working correctly with your app. If you’re using create-react-app, try running this:
Jest --coverage –t debug.test.js
For example, suppose you see a warning because your React component uses an undeclared variable, label, or function. In that case, you can fix this warning by adding an appropriate declaration to the component. To rerun your Jest tests after fixing errors or warnings, press Ctrl + R on Windows/Linux or Cmd + R on macOS. You can rerun Jest with the –watch flag, which will keep Jest open in a temporary window, and rerun tests automatically as you make changes. The benefit of using –watch is that it’ll be faster because it won’t run the complete Jest build step each time. To see how this works, you can use asyncWatch, a script on the create-react-app scaffolding:
cd <path to your react application> && npm i && npm run asyncWatch
This command keeps Jest running in the background and watches all of the components in src/jsx. So, a change in the component will automatically rerun all of your tests. When you find a bug, you could set breakpoints to keep track of code and narrow the scope.
Refactoring Old Code
The React Testing Library introduces several methods (verify, setup, teardown, injectAsync, and onAfterChange). These methods allow us to assert a particular state of our component at different time points in the browser’s lifecycle. The only new method is onAfterChange. It allows us to perform actions after we interact with our component. For instance, let’s say we have a text input that will enable us to input a new username. When we provide an invalid name, the invalid name will trigger the onAfterChange method. The method allows us to perform an assertion to ensure that the provided value is invalid.
import TestUtils from 'react-testing-library'; import React from 'react'; const UsernameInput = (props) => { return ( <div> <label>Username</label> <input type="text" value={props.value} onChange={(e) => {TestUtils.Simulate.change(e.target,{target:{value:''}})}}/> </div> ); } export default UsernameInput;
So, if we wanted to test the functionality of this UsernameInput component, we could test that the onAfterChange method is hit when the invalid value has been changed. This comes in handy when you’re refactoring old code and need to ensure a bug hasn’t been introduced due to the change. However, it requires you to understand your component and what should happen in any given scenario. Also, it requires some knowledge of JavaScript and how to perform assertions using ReactTestUtils.
Missing Element
The most typical issue you’ll encounter is not being able to return an element. You may have experienced one of these errors:
- You have an undefined value and are looking for a variable.
- You’re trying to get the parent of an element not in the DOM hierarchy.
- Your CSS selector is slightly off, and you’re trying to find a descendant of a DOM node you’re referencing in some way—either with CSS, JavaScript, or React’s findDOMNode() helper method.
Uncaught exception: React.findDOMNode is not a function or a property of this type React.findDOMNode is not a function or a property of this type
Another way to view these errors would be to see them grouped in the console log: ReactTestUtils.<Fixture>.debugLog in your terminal. You’ll see something like this if you hit “Home” and then “Red”:
debug log> red debug log {const element = findElement("Home");} undefined; <Homereact.findElement.enabled == false > App.routes.default.map(<Route path ="/" component={Home}/>);
The first line is what we’re looking for. It’s a simple assertion: “Find an element called ‘Home’ and return it.” If you see this line, but that element isn’t found, your test failed. The test failure could be a bug in your application or one of two things: either React isn’t finding the element you want it to find, or you’re using JSX syntax that changes how React finds elements. And the other way is to use the built-in testing utility debugger. It will print out every assertion it runs and a stack trace if there’s an error while debugging. To use it, do
import { Assert} from "react-testing-library" ; const MyComponent = ( < div> Hello World </div> ); const MyComponent2 = React.createClass ({ onClick : function () { Assert.equal(this.props.onClick(), "Hello World" )}}); const MyComponent3 = React . createClass ({ onClick : function () { Assert . equals ( this.props.onClick (), "Hello World" ); } }); const TestInstance = TestUtils . createTestingInstance ( MyComponent , MyComponent2 , MyComponent3 );
Bugs from typos and system errors can be a bane to any programmer’s existence. Therefore, how do we ensure our code is free of these mistakes? Verify, beginning with the most common error, that your query (getByText, getByRole, getByPlaceholderText, getByTitle, and getByTestId) matches the element’s characteristics. Copy and paste the right content to ensure that a typo isn’t the cause of a missing element.
Test Fails When Run With Other Tests
One of the most aggravating scenarios is when a single test passes, yet the entire suite fails when executed. Here are some potential solutions to that issue. It’s conceivable that your component modifies global variables. During the execution of one of your tests, data may be stored in localStorage, sessionStorage, or on the window object. This can be problematic if the subsequent test expects to function with a pristine copy of these storage methods. Ensure that these variables are reset in your beforeEach.
let student; beforeEach(() => { student = { grade: 'A' }; });
Further, async is perhaps the most prevalent reason why tests fail when run concurrently. When a test does an operation that should be awaited but lacks one, it effectively executes the code after it has been done. This may wreak havoc on the subsequent examination, forcing it to fail. Use eslint-plugin-testing-library to ensure that no async are missing from React Testing Library methods. This will alert you if you are utilizing async needlessly or not at all. As for the functions you’re calling from your test, you’ll need to scrutinize them to ensure that you include the async keyword.
Router Params Don’t Exist
When testing a component rendered under a react-router Route component in your application, you must ensure that the path is identical in both contexts. You’ve got to set the exact location in your browser, and then you can use this library’s findRoutes() method to get a list of the routes available on the current page. The best part is that now you can watch this console log all the errors in your tests.
import { findRoutes } from 'react-testing-library'; //some boilerplate setup code here ... const routes = await findRoutes(); //a bunch of react component tests that assert on their rendered output which are rendered against these routes //Note that the path of the route is important, so we do: <Route path="/foo" /> const routes = await findRoutes() //a bunch of react component tests that assert on their rendered output.
Shifting and maintaining focus between multiple places on the page, like testing with a tool like Jest, are arduous to debug. I’ve often found myself sitting by the console or in my terminal trying to figure out how half of my test case failed while I was otherwise focused on other parts of my app’s screen. This library eliminates any of that by telling you immediately what happened.
Element Not Removed Immediately
Occasionally, you might need to guarantee that an element is removed from the DOM; however, your test fails to comply. Trying to turn green can be daunting. Luckily with React Testing Library, you can leverage waitForElementToBeRemoved, a waitFor wrapper that’s essential in situations when an action doesn’t delete an element. Perhaps it performs an API request, and the promise callback is responsible for removing it.
Conclusion
Testing can be daunting, especially when most of your tests fail and you’re trying to debug. The highlighted steps should give you a head start and reference point when debugging. However, for a more satisfying solution, you should try Testim, as it reduces your work by leveraging the power of AI to give you faster, stable authoring of functional UI tests.
Expand Your Test Coverage