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