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
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 ():
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.
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.
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.
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)')
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
);
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With