How to Test Render Props in React
Testing render props in React ensures that components sharing code via function props behave correctly under various states. This article provides a straightforward guide on how to test React components utilizing the render props pattern using React Testing Library and Jest, focusing on verifying both the arguments passed to the render function and the final rendered output.
The Render Prop Component
To understand how to test this pattern, consider a standard
Toggle component that uses a render prop (passed as
children) to share its toggle state and toggle
function:
import React, { useState } from 'react';
const Toggle = ({ children }) => {
const [on, setOn] = useState(false);
const toggle = () => setOn(!on);
return children({ on, toggle });
};
export default Toggle;Testing Strategy
When testing a render prop component, you should verify two key behaviors: 1. Argument Delivery: The component calls the render prop function with the correct arguments (state and helpers). 2. UI Updates: The component correctly updates its state and re-renders when the consumer triggers state-changing functions.
Writing the Test
Using React Testing Library and Jest, you can render the component with a mock function as its child. This allows you to assert on both the arguments passed to the function and the DOM changes.
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Toggle from './Toggle';
test('provides state and control functions to the render prop', async () => {
// 1. Create a mock render prop function
const renderPropMock = jest.fn(({ on, toggle }) => (
<div>
<span data-testid="status">{on ? 'ON' : 'OFF'}</span>
<button onClick={toggle}>Toggle Button</button>
</div>
));
// 2. Render the component with the mock render prop
render(<Toggle>{renderPropMock}</Toggle>);
// 3. Assert the render prop was called
expect(renderPropMock).toHaveBeenCalled();
// 4. Assert the initial arguments passed to the render prop
expect(renderPropMock).toHaveBeenLastCalledWith(
expect.objectContaining({ on: false, toggle: expect.any(Function) })
);
expect(screen.getByTestId('status')).toHaveTextContent('OFF');
// 5. Simulate user interaction to trigger state change
await userEvent.click(screen.getByRole('button', { name: /toggle button/i }));
// 6. Assert the render prop was called with the updated state
expect(renderPropMock).toHaveBeenLastCalledWith(
expect.objectContaining({ on: true })
);
expect(screen.getByTestId('status')).toHaveTextContent('ON');
});Best Practices for Testing Render Props
- Mock the Render Prop Function: Use
jest.fn()to wrap the JSX returned by the render prop. This lets you use Jest matchers liketoHaveBeenLastCalledWithto inspect the exact arguments passed to your render prop. - Test the Interface, Not Implementation: Focus on what the user sees and interacts with (e.g., clicking buttons and reading text) rather than spying directly on internal React state hooks.
- Use
expect.objectContaining: Since the render prop might receive multiple arguments or complex objects, useexpect.objectContainingto test only the specific properties you care about.