Avoid hardcoding testid

Avoid hardcoding testid

Tags
Web Dev
React
React Testing Library
Unit Test
Published
February 8, 2023
Author
When building a web application, you need to ensure that your components are well-tested. One of the ways to test your application is to use a library like @testing-library. This library provides you with functions to render your components and interact with them, making it easier to test your app.
One of the key elements in making your components testable is to add unique data-testid attributes to them. These attributes act as selectors that help the testing library identify the element you want to test. However, it’s important to avoid hardcoding these attributes in your components, as doing so might lead to false positives when testing your app.
Here’s an example of a hardcoded data-testid:
import React from 'react'; interface ButtonProps { label: string; onClick: () => void; } const Button: React.FC<ButtonProps> = ({ label, onClick }) => { return ( <button data-testid="submit-button" onClick={onClick}> {label} </button> ); }; export default Button;
Let’s test if it’s working as expected:
import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import Button from './Button'; describe('Button component', () => { it('should call onClick function when button is clicked', () => { const mockOnClick = jest.fn(); const { getByTestId } = render( <Button label="Submit" onClick={mockOnClick} /> ); const submitButton = getByTestId('submit-button'); fireEvent.click(submitButton); expect(mockOnClick).toHaveBeenCalled(); }); });
As you can see, the test passes, but if you have multiple instances of this component on a page, the hardcoded data-testid will lead to incorrect test results.
As mentioned earlier, hardcoding data-testid attributes in reusable components might lead to false positives when testing your app. Take a look at this example:
import React from 'react'; interface ButtonProps { label: string; onClick: () => void; } const Button: React.FC<ButtonProps> = ({ label, onClick }) => { return ( <button data-testid="submit-button" onClick={onClick}> {label} </button> ); }; const App = () => { return ( <div> <Button label="Submit 1" onClick={() => console.log('Button 1 clicked')} /> <Button label="Submit 2" onClick={() => console.log('Button 2 clicked')} /> </div> ); }; export default App;
Now, let’s write a test for this App component, where we want to check if both buttons are working as expected:
import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import App from './App'; describe('App component', () => { it('should call the onClick function of both buttons when they are clicked', () => { const mockOnClick1 = jest.fn(); const mockOnClick2 = jest.fn(); const { getByTestId } = render( <App /> ); const submitButton1 = getByTestId('submit-button'); fireEvent.click(submitButton1); expect(mockOnClick1).toHaveBeenCalled(); const submitButton2 = getByTestId('submit-button'); fireEvent.click(submitButton2); expect(mockOnClick2).toHaveBeenCalled(); }); });
In this case, even though we are using different onClick functions for both buttons, the test will still pass, because both buttons have the same data-testid of submit-button. This is a clear example of how hardcoded testids can lead to false positives in testing.
To avoid this issue, you should pass data-testid attributes as props, so each component instance can have a unique value. This will help ensure that your tests are accurate and reliable. Here’s an example:
import React from 'react'; interface ButtonProps { label: string; onClick: () => void; testId: string; } const Button: React.FC<ButtonProps> = ({ label, onClick, testId }) => { return ( <button data-testid={testId} onClick={onClick}> {label} </button> ); }; export default Button;
And here’s the updated test for this component:
import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import Button from './Button'; describe('Button component', () => { it('should call onClick function when button is clicked', () => { const mockOnClick = jest.fn(); const { getByTestId } = render( <Button label="Submit" onClick={mockOnClick} testId="submit-button-1" /> ); const submitButton = getByTestId('submit-button-1'); fireEvent.click(submitButton); expect(mockOnClick).toHaveBeenCalled(); }); });
With this approach, you can render multiple instances of the component and each instance will have a unique testid, allowing you to test each component instance individually.
I hope you enjoyed it and learned something new. In that case, please share it with your friends and colleagues 😃
Cheers! 🍻