How I Learned Unit Testing with React Testing Library (RTL) by Implementing It in a Real Project

I am Saurabh Gupta, a dedicated frontend developer with over 2.5 years of experience. My expertise lies in building user-friendly and responsive web applications using technologies like React and Next.js. I have a strong background in integrating APIs and working with modern JavaScript libraries. I am passionate about creating efficient, scalable, and aesthetically pleasing web solutions, and I continuously seek to refine my skills to deliver impactful user experiences.
For a long time, unit testing was the most ignored part of my React learning journey.
Not because I didn’t care about quality — but because testing always felt abstract.
Most tutorials showed isolated examples that didn’t resemble real applications.
So I decided to change the approach.
Instead of learning unit testing first and building later, I learned React Testing Library (RTL) while implementing it inside a real project.
This article documents everything:
mindset
setup
writing test cases
handling user interactions
understanding coverage
mistakes I made
how testing changed my development style
If you’re a React developer who feels uncomfortable with testing — this is for you.
Why I Finally Took Unit Testing Seriously
Three things pushed me:
Interviews
Almost every React interview includes questions like:How do you test components?
Difference between Jest and RTL?
How do you test user interaction?
Fear of Refactoring
Small UI changes were breaking existing functionality.Realization
Manual testing does not scale.
At some point, I understood:
Testing is not for testers.
Testing is for developers who want confidence.
My Learning Strategy (Very Important)
Before touching any tool, I fixed my mindset:
❌ What I avoided
Memorizing RTL APIs
Writing tests just for coverage
Testing implementation details
✅ What I focused on
Testing what the user sees
Testing how the user interacts
Testing real behavior
This is exactly what React Testing Library promotes.
Step 1: Understanding What RTL Actually Is
React Testing Library is not about testing React components.
It is about testing:
How your app behaves from the user’s perspective
That means:
No checking state directly
No accessing component instances
No shallow rendering mindset
Step 2: Setting Up Testing in the Project
I already had a React project.
I added RTL with the following packages:
npm install --save-dev @testing-library/react
npm install --save-dev @testing-library/jest-dom
Setup File
// setupTests.js
import '@testing-library/jest-dom';
This enabled readable assertions like:
toBeInTheDocumenttoBeDisabledtoHaveTextContent
Step 3: Writing the First Test (Confidence Booster)
Component: Button.jsx
const Button = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
export default Button;
Test: Button.test.jsx
import { render, screen } from '@testing-library/react';
import Button from './Button';
test('renders button with correct label', () => {
render(<Button label="Click Me" />);
const button = screen.getByText('Click Me');
expect(button).toBeInTheDocument();
});
What I Learned Here
render()mounts the componentscreenrepresents what the user seesTests read like English
This was the moment testing started making sense.
Step 4: Testing User Interaction (The Core of RTL)
Real apps are interactive.
So I tested click behaviour.
import { render, screen, fireEvent } from '@testing-library/react';
test('calls onClick when button is clicked', () => {
const handleClick = jest.fn();
render(<Button label="Submit" onClick={handleClick} />);
fireEvent.click(screen.getByText('Submit'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
Key Takeaway
jest.fn()helps track function callsfireEventsimulates real user actionsWe test effects, not implementation
Step 5: Testing a Real Form (Project-Level Example)
Component: LoginForm.jsx
import { useState } from 'react';
const LoginForm = () => {
const [email, setEmail] = useState('');
return (
<>
<input
placeholder="Enter email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button disabled={!email}>Login</button>
</>
);
};
export default LoginForm;
Test Case
import { render, screen, fireEvent } from '@testing-library/react';
import LoginForm from './LoginForm';
test('login button enables when email is entered', () => {
render(<LoginForm />);
const input = screen.getByPlaceholderText('Enter email');
const button = screen.getByText('Login');
expect(button).toBeDisabled();
fireEvent.change(input, {
target: { value: 'test@example.com' },
});
expect(button).toBeEnabled();
});
Why This Matters
This test:
Covers conditional rendering
Covers state update
Covers UI behaviour
Mimics real user flow
Step 6: Learning Query Priority (Very Important)
RTL recommends this order:
getByRolegetByLabelTextgetByPlaceholderTextgetByTextgetByTestId(last option)
Example
screen.getByRole('button', { name: /login/i });
This:
Improves accessibility
Makes tests more resilient
Encourages better HTML semantics
Step 7: Running Coverage Report
npm test -- --coverage
Coverage report shows:
Statements
Branches
Functions
Lines
What Coverage Taught Me
Lines → Did the code execute?
Branches → Did conditions run?
Functions → Were functions actually called?
High coverage does not guarantee quality
But low coverage guarantees risk
Step 8: Improving Coverage the Right Way
Instead of fake tests, I added:
Empty State Test
test('error message is not visible initially', () => {
render(<Form />);
expect(screen.queryByText('Error')).not.toBeInTheDocument();
});
Conditional Rendering Test
test('shows loader when loading is true', () => {
render(<Component loading />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
Common Mistakes I Made (Learn From This)
❌ Testing internal state
❌ Overusing data-testid
❌ Writing tests only for coverage
❌ Avoiding tests during development
✅ Test behaviour
✅ Test user interaction
✅ Write tests alongside components
How Unit Testing Changed My Development Style
After RTL:
I write smaller components
I refactor without fear
UI bugs reduce significantly
Code reviews become easier
Interview confidence increased
Testing became part of development, not an afterthought.
Final Thoughts
I didn’t learn React Testing Library by reading documentation.
I learned it by:
Breaking things
Writing wrong tests
Fixing them
Seeing coverage improve
Gaining confidence
If you’re avoiding unit testing — don’t aim for perfection.
Aim for clarity and confidence.
Start small.
One test.
One component.
That’s how it begins.



