I remember the early days of writing front-end tests. Iād stare at the DOM, wondering how to simulate what users actually doāclicking buttons, typing in fields, changing options. Thatās when I discovered fireEvent
from React Testing Library, and everything started making sense.
While testing user interfaces, itās easy to fall into the trap of directly calling functions or triggering effects manually. But that doesnāt reflect what real users do. If your goal is to build user-centric and robust tests, fireEvent
is your go-to weapon.
In this blog, Iāll guide you through how fireEvent
works with React Testing Library and Jest, practical use cases, common mistakes, and how to combine it with assertion best practices to create meaningful, user-driven tests.
The Problem: Manual Function Calls ā Real User Interactions
Hereās what I did wrong in my early tests:
import { render } from '@testing-library/react';
import Counter from './Counter';
test('increments the counter', () => {
const { container } = render(<Counter />);
// Directly calling the function instead of simulating the user action
container.querySelector('button').click();
expect(container.textContent).toContain('1');
});
This technically works, but it bypasses Reactās event system, and doesnāt test actual browser behavior. Thatās where fireEvent
steps in.
What is fireEvent
?
fireEvent
is a utility from React Testing Library that allows you to simulate DOM events (like click
, change
, submit
, keydown
, etc.) in testsājust like a real user interaction would trigger them.
Unlike calling onClick
manually, fireEvent
goes through the proper bubbling, propagation, and synthetic event handling that React expects.
Getting Started with fireEvent
First, make sure you have:
npm install --save-dev @testing-library/react @testing-library/jest-dom
Letās start with a simple exampleāa counter.
// Counter.js
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p data-testid="count">{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
}
export default Counter;
Now, test it using fireEvent
:
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter on click', () => {
render(<Counter />);
const button = screen.getByText('Increment');
fireEvent.click(button);
expect(screen.getByTestId('count')).toHaveTextContent('1');
});
And just like that, youāre simulating real user clicks in Jest!
Common fireEvent Use Cases
1. Text Input
<input type="text" onChange={(e) => setName(e.target.value)} />
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'John' } });
ā Simulates typing into an input.
2. Form Submission
fireEvent.submit(screen.getByRole('form'));
This triggers a formās onSubmit
handler as if the user pressed Enter or clicked a submit button.
3. Checkboxes and Radios
fireEvent.click(screen.getByLabelText('Accept Terms'));
Or directly:
fireEvent.change(screen.getByLabelText('Accept Terms'), { target: { checked: true } });
4. Keyboard Events
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
Great for testing accessibility and keyboard-driven navigation.
fireEvent vs userEvent
You might ask: should I even use fireEvent
anymore?
Well, fireEvent
is low-level, whereas userEvent
is built to mimic more realistic user interactions.
Hereās the difference:
// fireEvent
fireEvent.change(input, { target: { value: 'React' } });
// userEvent (recommended)
await userEvent.type(input, 'React');
Use fireEvent
when:
- Youāre testing simple components.
- You want full control over event payloads.
- You need custom event triggers (like programmatic
focus
orblur
).
But for simulating full flows, userEvent
feels more natural.
Best Practices Iāve Learned
ā Always Test Behavior, Not Implementation
Donāt test internal state or call setState
directly. Use fireEvent
to drive your UI and assert outcomes that the user sees.
ā
Use screen
for Better Readability
Avoid destructuring getByText
etc. from the render result. screen
is recommended:
fireEvent.click(screen.getByText('Submit'));
ā
Assert with @testing-library/jest-dom
Make assertions more expressive:
expect(button).toBeDisabled();
expect(input).toHaveValue('hello');
expect(div).toBeVisible();
Debugging fireEvent Failures
When tests fail, try:
screen.debug();
It prints the current DOM tree, helping you figure out whatās happening.
Also, always double-check your element selectors and make sure await
is used properly if youāre testing async behavior.
If you want a solid async test guide, check out my post:
š Top 10 React Testing Library Queries Every Developer Should Know
fireEvent with Controlled Components
Letās test a controlled input:
function NameInput() {
const [name, setName] = useState('');
return (
<input
aria-label="name-input"
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
Test:
fireEvent.change(screen.getByLabelText('name-input'), {
target: { value: 'Waleed' },
});
expect(screen.getByLabelText('name-input')).toHaveValue('Waleed');
Final Thoughts
Testing isnāt just about making sure code ārunsāāitās about ensuring the user gets what they expect.
fireEvent
helped me shift my mindset from unit testing to behavior-driven testing. By simulating real user actions, I write better code and gain confidence in every UI change.
Start small. Test a button click. A form submission. Then build up. I promise, writing clean, readable, and reliable tests will become second nature.