3

The latest versions of Material UI now have a Hooks alternative for styling components, instead of the HoC. So instead of

const styles = theme => ({
  ...
});

export const AppBarHeader = ({ classes, title }) => (
  ...
);
export default withStyles(styles)(AppBarHeader);

you can choose to do this instead:

const useStyles = makeStyles(theme => ({
  xxxx
}));

const AppBarHeader = ({ title }) => {
  const classes = useStyles();
  return (
    ....
  )
};

export default AppBarHeader;

In some ways this is nicer, but as with all hooks you can no longer inject a 'stub' dependency to the component. Previously, for testing with Enzyme I just tested the non-styled component:

describe("<AppBarHeader />", () => {
  it("renders correctly", () => {
    const component = shallow(
      <AppBarHeader title="Hello" classes="{}" />
    );
    expect(component).toMatchSnapshot();
  });
});

However, if you use hooks, without the 'stub' dependency for classes, and you get:

  Warning: Material-UI: the `styles` argument provided is invalid.
  You are providing a function without a theme in the context.
  One of the parent elements needs to use a ThemeProvider.

because you always need a provider in place. I can go and wrap this up:

describe("<AppBarHeader />", () => {
  it("renders correctly", () => {
    const component = shallow(
      <ThemeProvider theme={theme}>
        <AppBarHeader title="Hello" classes="{}" />
      </ThemeProvider>
    ).dive();
    expect(component).toMatchSnapshot();
  });
});

but that no longer seems to render the children of the component (even with the dive call). How are folks doing this?

James Crowley
  • 3,911
  • 5
  • 36
  • 65

1 Answers1

1

EDIT: As per the comments below, this implementation presents some timing issues. Consider testing with mount instead of shallow testing, or use the withStyles HOC and export your component for shallow rendering.

So I have been grappling with this for a day now. Here is what I have come up with.

There are some issues trying to stub makeStyles since it appears MUI has made it readonly. So instead of creating a useStyles hook in each component, I created my own custom useStyles hook that calls makeStyles. In this way I can stub my useStyles hook for testing purposes, with minimal impact to the flow of my code.

// root/utils/useStyles.js
// My custom useStyles hook

import makeStyles from '@material-ui/styles/makeStyles';

export const useStyles = styles => makeStyles(theme => styles(theme))();

Its almost like using the withStyles HOC

// root/components/MyComponent.js
import React from 'react';
import { useStyles } from '../util/useStyles';

// create styles like you would for withStyles
const styles = theme => ({
  root: {
    padding: 0,
  },
});

export const MyComponent = () => {
  const classes = useStyles(styles);
  return(
    </>
  );
}
// root/component/MyComponent.spec.js
import { MyComponent } from './MyComponent';
import { shallow } from 'enzyme';
import { stub } from 'sinon';

describe('render', () => {
  it('should render', () => {
    let useStylesStub;
    useStylesStub = stub(hooks, 'useStyles');
    useStylesStub.returns({ });
    const wrapper = shallow(<MyComponent />);
    console.log('profit');
  });
});

This is the best I can come up with for now, but always open to suggetions.

  • 1
    This approach is problematic. `makeStyles` is intended to be called once per styles object, but with your approach it will be called once per render. Each render will use a new version of the custom hook returned by `makeStyles`. This will also negatively affect the order of the style sheet generated (see my answer [here](https://stackoverflow.com/questions/57220059/internal-implementation-of-makestyles-in-react-material-ui/57226057#57226057) for explanation). You would be much better off to use `withStyles` than to use this approach. – Ryan Cogswell Jul 29 '19 at 22:41
  • Also see my answer [here](https://stackoverflow.com/questions/56929702/material-ui-v4-makestyles-exported-from-a-single-file-doesnt-retain-the-styles/56941531#56941531) for further insight into the problems this approach will cause. – Ryan Cogswell Jul 29 '19 at 22:43
  • Thanks for the update, I was worried this would cause issues – Rowan Baker-French Jul 29 '19 at 22:51
  • 1
    useEffect will not have the appropriate timing to manage this correctly. My recommendation is to change the testing approach to not use shallow rendering as @JamesCrowley mentioned in his comment rather than doing weird workarounds in your code. – Ryan Cogswell Jul 29 '19 at 23:10
  • Could one not just memoize the `useStyles` to solve the re-render issues? – Rowan Baker-French Jul 29 '19 at 23:10
  • 1
    The timing of `makeStyles` will still be off. For the interaction between parent and child component styles to work appropriately, `makeStyles` should be called when the component is imported rather than when it is rendered. – Ryan Cogswell Jul 29 '19 at 23:13