DEV Community

Danijel Maksimovic
Danijel Maksimovic

Posted on

Unit testing render prop component in React

What is a render prop?

Render prop is a pattern that is widely used in React ecosystem. In a nutshell, render prop is a pattern in which you are passing a function as a prop, usually called render or more commonly as a children prop. For example:

import React from 'react';

const RenderPropComponent = ({children}) => {
  const [counter, setCounter] = React.useState(0)  

  return children({counter, setCounter});
};

// usage
const Usage = () => {
  return (
    <RenderPropComponent>
      {({counter}) => <p>Counter: {counter}</p>}
    </RenderPropComponent>
  );
};
Enter fullscreen mode Exit fullscreen mode

If you want to read more extensively about render props pattern in React and their usage in React ecosystem check this post.

Test preparation

In order to test render prop component, we should first write one! Our component will fetch posts from an API and expose loading state and posts to a consumer component.

import React from 'react';
import PropTypes from 'prop-types';

import { fetchPosts } from './api';

export default class FetchPosts extends React.Component {
  static propTypes = {
    children: PropTypes.func.isRequired
  };

  state = { posts: [], loading: false };

  async componentDidMount() {
    this.setState({ loading: true });

    const posts = await fetchPosts();

    this.setState({ posts, loading: false });  
  }

  render() {
    return this.props.children({posts: this.state.posts, loading});
  }
}
Enter fullscreen mode Exit fullscreen mode

Writing the test

We are going to write our test using jest and react-testing-library but the same principles apply if you use something else to write your tests.

import React from 'react';
import { render } from 'react-testing-library';

import FetchPosts from './FetchPosts';

const mockPosts = [{ id: 1, title: 'Title' }];

jest.mock('./fetchPosts', () => Promise.resolve(mockPosts));

describe('FetchPosts component test', () => {
  it('should expose loading and posts prop', () => {
    const postsCallbackMock = jest.fn();

    const { getByTestId } = render(
      <FetchPosts>{postsCallbackMock}</FetchPosts>
    );

    expect(postsCallbackMock).toHaveBeenCalledWith({
      loading: false,
      posts: mockPosts
    })
  });
});

Enter fullscreen mode Exit fullscreen mode

This is one a bit simpler way to test render prop component. Another way is to write a consumer component which renders something on the page and then expect it matches with the data that you received. For example:

import React from 'react';
import { render } from 'react-testing-library';

import FetchPosts from './FetchPosts';

const mockPosts = [{ id: 1, title: 'Title' }];

jest.mock('./fetchPosts', () => {
  return new Promise(resolve => {
    setTimeout(() => resolve(mockPosts), 100);
  });
});

const FetchPostsConsumer = () => (
  <FetchPosts>
    {({loading, posts}) => {
      if(loading) return <span data-testid="loading"></span>;

      return posts.map(post => <p data-testid="post-title">{post.title}</p>)
    }}
  </FetchPosts>
);

describe('FetchPosts component test', done => {
  it('should return correct loading and posts props', () => {
    const postsCallbackMock = jest.fn();

    const { getByTestId } = render(
      <FetchPostsConsumer />
    );

    expect(getByTestId('loading').textContent).toBe('Loading');

    setTimeout(() => {
      expect(getByTestId('post-title').textContent).toBe('Title');
      done()
    })
  });
});

Enter fullscreen mode Exit fullscreen mode

At the beginning of this test, we are declaring what our fetchPosts module is returning so we can have the same results on each test run (these tests are called deterministic). This mocked version of a function is resolving a promise but after some timeout, which gives us enough time to inspect the loading state later in our test.

Next, we are declaring a component that is using render prop component that we really want to test. After the component is rendered we are checking if loading text is present. After some time, we are checking if the correct post is being rendered as a result of render prop callback. This approach is a bit longer but in my opinion, it gives us a bit more user-oriented test which in the end is how users are going to use our component.

Conclusion

As you can see testing render prop component is not that difficult in the end. Since that kind of a component doesn't generate an output by itself we have to provide that missing part in our test and then do the assertions. A simpler way is to just provide a mock function and expect that it's called with correct parameters. Which approach do you like more? Share it in the comments below 👇

Top comments (1)

Collapse
 
schemedesigns profile image
Justin Martin

Thanks for the post. This is exactly what I needed!