Search code examples
reactjsmaterial-uienzymememo

How do I shallow test a react component wrapped in memo and withStyles?


I have a component which is wrapped in both a Material-UI withStyles HOC and a React memo HOC.

I am unable to test this component as I am unable to call dive():

ShallowWrapper::dive() can only be called on components

The only option I am currently aware of is to independently export Demo and export default withStyles(styles)(Demo). This allows me to test the component that isn't wrapped in withStyles. I would like to avoid this method.

If I remove memo(), I am able to test the component. Likewise, if I remove withStyles(), I am also able to test the component. The combination of these HOCs render my component un-testable.

What are some available strategies to effectively test this component?

demo.js

import React, { memo } from "react";
import MUIIconButton from "@material-ui/core/IconButton";
import { withStyles } from "@material-ui/core/styles";
import Tooltip from "@material-ui/core/Tooltip";
import Typography from "@material-ui/core/Typography";

const styles = () => ({
  root: {
    backgroundColor: "red"
    /* more styles... */
  }
});

const Demo = memo(({ label, classes }) => (
  <div className={classes.root}>
    <Tooltip disableFocusListener title={label}>
      <Typography>label</Typography>
    </Tooltip>
  </div>
));

export default withStyles(styles)(Demo);

demo.test.js

import React from "react";
import Adapter from "enzyme-adapter-react-16";
import { configure, shallow } from "enzyme";
import Demo from "./demo";
import MUIIconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";

configure({ adapter: new Adapter() });

describe("Demo", () => {
  it("Should have a tooltip with label", () => {
    const tooltip = "My tooltip";

    const el = shallow(<Demo label={tooltip} />).dive();

    expect(el.find(Tooltip).props().title).toEqual(tooltip);
  });
});

Full working Sandbox

Edit 2j3o14zxy0


Solution

  • As skyboyer suggests, you should just export the memoized function. You can import the default export HOC and utilize mount, but you'll need to mock the classes object to match how it's being used within the component.

    Working example: https://codesandbox.io/s/4r492qvoz9

    components/Demo/demo.js

    import React, { memo } from "react";
    import MUIIconButton from "@material-ui/core/IconButton";
    import { withStyles } from "@material-ui/core/styles";
    import Tooltip from "@material-ui/core/Tooltip";
    import Typography from "@material-ui/core/Typography";
    
    const styles = () => ({
      root: {
        backgroundColor: "red"
        /* more styles... */
      }
    });
    
    export const Demo = memo(({ label, classes }) => {
      return (
        <div className={classes.root}>
          <Tooltip disableFocusListener title={label}>
            <Typography>label</Typography>
          </Tooltip>
        </div>
      );
    });
    
    export default withStyles(styles)(Demo);
    

    components/Demo/__tests__/demo.test.js if ever need to see the DOM structure, then just use console.log(wrapper.debug()); -- for example console.log(mountHOComponent.debug());)

    import React from "react";
    import Adapter from "enzyme-adapter-react-16";
    import { configure, shallow, mount } from "enzyme";
    import { Demo } from "../demo";
    import HOCDemo from "../demo";
    
    configure({ adapter: new Adapter() });
    
    const initialProps = {
      label: "My tooltip",
      classes: {
        root: "component-example"
      }
    };
    
    const shallowWrapper = shallow(<Demo {...initialProps} />);
    const mountWrapper = mount(<Demo {...initialProps} />);
    const mountHOComponent = mount(<HOCDemo {...initialProps} />);
    
    describe("Demo", () => {
      afterAll(() => {
        shallowWrapper.unmount();
        mountWrapper.unmount();
      });
    
      it("shallowWrap renders a tooltip with label", () => {
        expect(shallowWrapper.find("WithStyles(Tooltip)").props().title).toBe(
          initialProps.label
        );
      });
    
      it("mountWrap renders a tooltip with label", () => {
        expect(mountWrapper.find("Tooltip").props().title).toBe(initialProps.label);
      });
    
      it("mountHOComponent renders a tooltip with label", () => {
        expect(mountHOComponent.find("Tooltip").props().title).toBe(
          initialProps.label
        );
      });
    });