Material-ui: Using enzyme/shallow/mount and a theme with custom variables in a sub component

Created on 15 Jun 2018  路  13Comments  路  Source: mui-org/material-ui

  • [x] This is a v1.x issue (v0.x is no longer maintained).
  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior

Component that leverages theme + withStyles with custom variable doesn't fail. I have a component that I wrap with withStyles. I only wrap the top level index with withRoot (where the theme is added to context). How to easily use shallow, mount, etc. to test a sub-component? I.e. how do I get the theme into the context? I feel like this should be an easy fix, but I can't find a good example for this. See the test results in the codesandbox link.

Current Behavior

Test fails

Steps to Reproduce (for bugs)

See test results in https://codesandbox.io/s/wwkjkxzxj8

  1. in withRoot I added a custom theme variable success.main:
const theme = createMuiTheme({
  palette: {
    primary: {
      light: purple[300],
      main: purple[500],
      dark: purple[700]
    },
    secondary: {
      light: green[300],
      main: green[500],
      dark: green[700]
    }
  },
  success: {
    main: "pink"
  }
});
  1. Created sub-component Component which uses the theme variable:
import React from "react";
import { withStyles } from "@material-ui/core/styles";

const styles = theme => ({
  customClass: {
    color: theme.success.main
  }
});

class Component extends React.Component {
  render() {
    const { classes } = this.props;
    return <div className={classes.customClass}>Dude</div>;
  }
}

export default withStyles(styles)(Component);
  1. Although app runs fine in browser due to withRoot at the top level, if you run the tests, they barf because theme.success is undefined
Cannot read property 'main' of undefined

TypeError: Cannot read property 'main' of undefined
    at styles (https://ymv6ow7021.codesandbox.io/src/components/Component.js:26:28)
    at Object.create (https://ymv6ow7021.codesandbox.io/node_modules/@material-ui/core/styles/getStylesCreator.js:26:35)
    at WithStyles.attach (https://ymv6ow7021.codesandbox.io/node_modules/@material-ui/core/styles/withStyles.js:269:45)
    at new WithStyles (https://ymv6ow7021.codesandbox.io/node_modules/@material-ui/core/styles/withStyles.js:143:15)
    at ReactShallowRenderer.render (https://ymv6ow7021.codesandbox.io/node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:131:26)
    at eval (https://ymv6ow7021.codesandbox.io/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:287:35)
    at withSetStateAllowed (https://ymv6ow7021.codesandbox.io/node_modules/enzyme-adapter-utils/build/Utils.js:94:16)
    at Object.render (https://ymv6ow7021.codesandbox.io/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:286:68)
    at new ShallowWrapper (https://ymv6ow7021.codesandbox.io/node_modules/enzyme/build/ShallowWrapper.js:119:22)
    at shallow (https://ymv6ow7021.codesandbox.io/node_modules/enzyme/build/shallow.js:19:10)
    at shallowWithContext (https://ymv6ow7021.codesandbox.io/node_modules/@material-ui/core/test-utils/createShallow.js:35:19)
    at Object.eval (https://ymv6ow7021.codesandbox.io/src/components/Component.test.js:29:19)
  1. How to inject custom theme easily? Thanks!

Context

Easily test sub components without wrapping in too many HOC.

Your Environment

I used codesandbox provided link with latest create-react-app, enzyme, etc.

docs test

Most helpful comment

I think that it would be a good opportunity to add such in the documentation:

Component.js

import React from "react";
import { withStyles } from "@material-ui/core/styles";

const styles = theme => ({
  root: {
    color: theme.success.main
  }
});

class Component extends React.Component {
  render() {
    const { classes } = this.props;

    return <div className={classes.root}>Dude</div>;
  }
}

export default withStyles(styles)(Component);

Component.test.js

import React from "react";
import { shallow, mount } from "enzyme";
import { unwrap } from "@material-ui/core/test-utils";
import MuiThemeProvider from "@material-ui/core/styles/MuiThemeProvider";
import Component from "./Component";

const ComponentNaked = unwrap(Component);

describe("<Component />", () => {
  it("with shallow", () => {
    const wrapper = shallow(<ComponentNaked classes={{}} />);
    console.log("shallow", wrapper.debug());
  });

  it("with mount", () => {
    const wrapper = mount(
      <MuiThemeProvider
        theme={{
          success: {
            main: "#fff"
          }
        }}
      >
        <Component />
      </MuiThemeProvider>
    );
    console.log("mount", wrapper.debug());
  });
});

All 13 comments

+1 - having the exact same issue.

I think that it would be a good opportunity to add such in the documentation:

Component.js

import React from "react";
import { withStyles } from "@material-ui/core/styles";

const styles = theme => ({
  root: {
    color: theme.success.main
  }
});

class Component extends React.Component {
  render() {
    const { classes } = this.props;

    return <div className={classes.root}>Dude</div>;
  }
}

export default withStyles(styles)(Component);

Component.test.js

import React from "react";
import { shallow, mount } from "enzyme";
import { unwrap } from "@material-ui/core/test-utils";
import MuiThemeProvider from "@material-ui/core/styles/MuiThemeProvider";
import Component from "./Component";

const ComponentNaked = unwrap(Component);

describe("<Component />", () => {
  it("with shallow", () => {
    const wrapper = shallow(<ComponentNaked classes={{}} />);
    console.log("shallow", wrapper.debug());
  });

  it("with mount", () => {
    const wrapper = mount(
      <MuiThemeProvider
        theme={{
          success: {
            main: "#fff"
          }
        }}
      >
        <Component />
      </MuiThemeProvider>
    );
    console.log("mount", wrapper.debug());
  });
});

@oliviertassinari unwrap() seems to be missing from the type definitions (test-utils/index.d.ts)?

unwrap() seems to be missing from the type definitions

@kallebornemark Right, we need to add it 馃憤

@kallebornemark Care to give it a go?

Have you tried using dive()? http://airbnb.io/enzyme/docs/api/ShallowWrapper/dive.html

MUI also has some test utils to help: https://material-ui.com/guides/testing/#api

They wrap enzyme's shallow with some helpful options like untilSelector: "Recursively shallow renders the children until it can find the provided selector. It's useful to drill down higher-order components.". There's examples in the actual codebase. But I think you would use it like so:

E.g.

import * as React from 'react';
import {createShallow} from '@material-ui/core/test-utils';
import Foo from './Foo';

describe('<Foo />', () => {
  let shallow;

  beforeEach(() => {
    shallow = createShallow({untilSelector: 'Foo'});
  });

  it('should render', () => {
    const wrapper = shallow(<Foo />);
    console.log(wrapper.debug());
  });
});

@mynameistechno Thank you very much for the speedy reply. I somehow accidentally deleted my question. For anyone wondering in the future, my question was:
How to test components wrapped by withRoot() as shown in the tutorials.
withRoot() function sketch:

function withRoot(Component) {
  function WithRoot(props) {
    return (
      <MuiThemeProvider theme={theme}>
        <CssBaseline />
        <Component {...props} />
      </MuiThemeProvider>
    );
  }
  return WithRoot;
}

App.js sketch:

function App() {
  return (
    <Foo>
      <div>
        <Bar />
        <Bar />
      </div>
    </Foo>
  );
}

export default withRoot(App);

For your response, I tried to use dive() but couldn't manage to make it work. I can't seem to find the App component when diving into MuiThemeProvider.

As for the untilSelector you suggested, I always get this error:

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

I tried both:

const shallow = createShallow({ untilSelector: 'App' });
const shallow = createShallow({ untilSelector: App });

and then shallow rendered, they all gave me the error. It might be some version imcompatible issue.

But after some more research, thanks to your guide, I found a working solution for my current setup.

describe('App shallow tests', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = shallow(<App />).find('App').shallow();
  });

  it('contains a Foo', () => {
    expect(wrapper.find(Foo).length).toBe(1);
  });
});

Thank you again and I hope this would help others.

You CAN manually provide the context with the custom theme & custom variables to withRoot, withStyles etc (I was having the exact same problem with withStyles). See my comment here for how I provide that context to shallow.

@issuehuntfest has funded $40.00 to this issue. See it on IssueHunt

I had more or less the same problem, but in my case I was using withTheme, instead of withStyles.

I've adapted your codesandbox here with the test passing.

I'm only using react-testing-library, but the solution should work with your current test set up.

Here is the test updated:

import React from "react";
import { render, waitForElement, cleanup } from "react-testing-library";
import { StyledComponent } from "./Component";

afterEach(cleanup);

describe("Test SignIn", () => {
  test("it renders sign in text", async () => {
    const { getByText } = render(<StyledComponent />);
    await waitForElement(() => getByText(/Dude/i));
  });
});

I had to do a named export for this to work without passing the styles:

export const StyledComponent = withStyles()(Component);
export default withStyles(styles)(Component);

I've tried to fix this following the material-ui testing documentation and @oliviertassinari response, but I was not able to make it work.

This is more of a general question regarding testing react components. If you use a custom context then you are responsible for either mocking it or ensuring your components are only rendered in the actual context.

I guess most of the issues arise from using shallow rendering API which we no longer encourage. We had to refactor way too many test without anything actually breaking because of heavy usage of shallow rendering. We now use almost exclusively mount with aria selectors internally and keep usage of #instance or find(ComponentType) to a minimum.

I guess most of the issues arise from using shallow rendering API which we no longer encourage.

Agreed. I've also started to migrate away from shallow rendering with enzyme and using dom-testing-library instead. Additionally we use a render wrapper function for rendering in tests that includes all the context we need. Here is a simplified version built on examples from dom-testing-library that add our redux store and mui theme:

// test-utils.js
import {render} from 'react-testing-library';
import theme from '../ourCustomTheme';

export function renderWithStore(element, { initialState, store = createStore(reducer, initialState) } = {}){
  return {
    ...render(
      <Provider store={store}>
        <MuiThemeProvider theme={theme}>{element}</MuiThemeProvider>
      </Provider>
    ),
    store
  };
}

I think we can close this ticket.

:+1: for closing, unless @eps1lon wants to update the documentation.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

finaiized picture finaiized  路  3Comments

FranBran picture FranBran  路  3Comments

ryanflorence picture ryanflorence  路  3Comments

reflog picture reflog  路  3Comments

activatedgeek picture activatedgeek  路  3Comments