Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 949
Ed Allonby Avatar asked Mar 05 '19 13:03

Ed Allonby


People also ask

What is memo in react?

When a component is wrapped in React.memo (), React renders the component and memoizes the result. Before the next render, if the new props are the same, React reuses the memoized result skipping the next rendering. Let's see the memoization in action. The functional component Movie is wrapped in React.memo ():

Is it possible to wrap class-based components in react memo?

While possible, wrapping class-based components in React.memo () is undesirable. Extend PureComponent class or define a custom implementation of shouldComponentUpdate () method if you need memoization for class-based components. Imagine a component that usually renders with different props.

What is memoizedmovie in react?

The functional component Movie is wrapped in React.memo (): React.memo (Movie) returns a new memoized component MemoizedMovie. MemoizedMovie outputs the same content as the original Movie component, but with one difference — MemoizedMovie render is memoized.

What is the difference between jest and react testing?

The tests will pass if your hypothesis is correct and fail if it is wrong. Unlike your react components, your tests are not executed in the browser. Jest is the test runner and testing framework used by React. Jest is the environment where all your tests are actually executed.


2 Answers

When I wrap with memo, I get a shape that looks like this

import MemoizedFoo from './Foo'
console.log(MemoizedFoo) 

    { '$$typeof': Symbol(react.memo),
      type:
       { [Function: Foo]
         displayName: 'Foo',
         defaultProps: { theme: {} } },
      compare: null }

so in my jest test, i can get the inner component by referencing the type key

import MemoizedFoo from './Foo'
const Foo = MemoizedFoo.type

describe() { it() { shallow(Foo) ...etc } }

This is great for shallow unit tests.

If i was mounting a parent component and looking for children to be present, you could do something like this:

wrapper = mount(Layout)
wrapper.find('Memo(Foo)')
like image 153
lfender6445 Avatar answered Sep 21 '22 21:09

lfender6445


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
    );
  });
});
like image 41
Matt Carlotta Avatar answered Sep 22 '22 21:09

Matt Carlotta