How to Mock Event Pooling in React
React’s legacy event pooling system, which reused synthetic events to
improve performance in React 16 and earlier, often requires special
handling during unit testing. This article explains how event pooling
works, why it causes issues in tests, and how to effectively mock
synthetic events and the event.persist() method using Jest
and React Testing Library.
Understanding Event Pooling in React
In React 16 and earlier, React reused the same
SyntheticEvent object across different events to save
memory. Once an event handler executed, React nullified all properties
on the event object.
To use an event asynchronously (such as inside a
setTimeout or an API promise), developers had to call
e.persist() to remove the event from the pool. While React
17 completely removed event pooling, you may still encounter legacy
codebases or third-party libraries where you must mock this
behavior.
Why Mock Event Pooling?
When testing event handlers that call e.persist(),
passing a simple object mock like
{ target: { value: 'test' } } will result in a runtime
error:
TypeError: e.persist is not a function
To prevent this error and ensure your event handlers run correctly
under test conditions, you must mock the persist function
on your event objects.
How to Mock
event.persist() in Jest
The most common way to mock React’s event pooling in unit tests is by
providing a mock implementation of the persist method using
Jest.
Basic Mocking in Component Tests
When simulating an event using React Testing Library or Enzyme, you
can pass a custom event object that includes a mocked
persist function.
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
test('should handle asynchronous input change', () => {
render(<MyComponent />);
const input = screen.getByRole('textbox');
// Create a mock event with a mocked persist function
const mockEvent = {
target: { value: 'Hello World' },
persist: jest.fn() // Mocks e.persist()
};
// Fire the event with the mocked event object
fireEvent.change(input, mockEvent);
expect(mockEvent.persist).toHaveBeenCalled();
expect(screen.getByText('Hello World')).toBeInTheDocument();
});Mocking Globally or in Helper Functions
If you have many tests that require mock events with pooled behavior, you can create a helper utility to generate these mock events.
export const createMockHtmlEvent = (values = {}) => {
return {
...values,
persist: jest.fn(),
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
};
};
// Usage in a test
const event = createMockHtmlEvent({ target: { value: 'test-value' } });Simulating Actual Pooling Behavior (Advanced)
If you specifically need to test that an event handler fails
without e.persist() (to ensure regression
protection for legacy React 16 applications), you can mock the pooling
mechanism by nullifying the properties after the event loop tick.
function createPooledEvent(data) {
const event = {
...data,
isPersisted: false,
persist() {
this.isPersisted = true;
}
};
// Simulate React nullifying the properties asynchronously
setTimeout(() => {
if (!event.isPersisted) {
Object.keys(data).forEach((key) => {
event[key] = null;
});
}
}, 0);
return event;
}By utilizing these mocking strategies, you can ensure your test suites remain robust and error-free when handling legacy React codebases that rely on event pooling.