Daniel
Daniel

Reputation: 3272

How can I test arguments passed to a render prop?

I have the following component, Layout:

const Layout = ({ children, data, ...otherProps }) => (
  <ErrorBoundary>
    <App render={({ isSidebarOpen, scrollTop, toggleSidebar }) => (
      <React.Fragment>
        <Helmet
          title={get(data, 'site.siteMetadata.title')}
          meta={[
            { name: 'description', content: get(data, 'site.siteMetadata.description') },
            { name: 'pinterest', content: 'nopin' },
            { name: 'og:title', content: 'Daniel Spajic' },
            { name: 'og:description', content: get(data, 'site.siteMetadata.description') },
            { name: 'og:type', content: 'website' },
            { name: 'og:url', content: get(data, 'site.siteMetadata.siteUrl') },
            { name: 'og:image', content: ogImage },
            { name: 'og:locale', content: 'en_AU' },
          ]}
        >
          <link rel="shortcut icon" type="image/png" href={favicon} />
          <link href="https://afeld.github.io/emoji-css/emoji.css" rel="stylesheet" />
        </Helmet>
        <div id={PAGE_CONTENT_CONTAINER_ID}>
          <Sidebar isOpen={isSidebarOpen} toggle={toggleSidebar} />
          <div id={PAGE_CONTENT_ID}>
            {children({ scrollTop, toggleSidebar, ...otherProps })}
          </div>
        </div>
      </React.Fragment>
    )}
    />
  </ErrorBoundary>
);

As shown it renders an App with a render prop. The isSidebarOpen and scrollTop arguments for the prop are both from App's state. toggleSidebar is one of App's methods.

I want to test a few things:

  1. The rendered Sidebar sets its toggle prop to toggleSidebar, and isOpen prop to isSidebarOpen
  2. The children function is called with an object containing scrollTop, toggleSidebar, and otherProps as its argument

These involve retrieving App's state and methods for comparison. I've tried accessing its state with Enzyme, but it's not possible because state() can only be accessed on the root node:

ShallowWrapper::state() can only be called on the root

Therefore how can I access App's state and methods so I can test these things?

Upvotes: 1

Views: 313

Answers (1)

Estus Flask
Estus Flask

Reputation: 222493

ShallowWrapper::state() can only be called on the root may not be a problem because tested values should be preferably hard-coded in unit tests. It's better to make a test unintentionally fail where it should pass than to make it unintentionally pass where it should fail, the former is much easier to debug and fix.

Though it may be beneficial to get component state, at least for assertions.

const layoutWrapper = mount(<Layout/>);
const appWrapper = layoutWrapper.find(App).dive();

expect(appWrapper.state('isSidebarOpen')).toBe(false);
expect(appWrapper.first(Sidebar).props('isOpen').toBe(false);

appWrapper.setState({ isSidebarOpen: true });

expect(appWrapper.state('isSidebarOpen')).toBe(true);
expect(appWrapper.first(Sidebar).props('isOpen').toBe(true);

There's a lot of moving parts in this component, this is also suggested by that it should be tested with mount and not shallow. it may be beneficial to provide fine-grained isolated tests, i.e. test render prop separately:

const layoutWrapper = mount(<Layout/>);
const appWrapper = layoutWrapper.first(App);
const Child = appWrapper.prop('render');

const childWrapper = shallow(<Child isSidebarOpen={false} ... />);

expect(childWrapper.find(Sidebar).props('isOpen').toBe(false);
...

And how App state interacts with render prop should be tested in dedicated App component test.

Upvotes: 1

Related Questions