Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using enzyme/shallow/mount and a theme with custom variables in a sub component #11864

Closed
2 tasks done
mynameistechno opened this issue Jun 14, 2018 · 13 comments · Fixed by #16368
Closed
2 tasks done
Labels
docs Improvements or additions to the documentation test

Comments

@mynameistechno
Copy link

mynameistechno commented Jun 14, 2018

  • This is a v1.x issue (v0.x is no longer maintained).
  • 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.

@Sylvestre67

This comment has been minimized.

@oliviertassinari oliviertassinari self-assigned this Jun 21, 2018
@oliviertassinari oliviertassinari added docs Improvements or additions to the documentation good first issue Great for first contributions. Enable to learn the contribution process. labels Jun 21, 2018
@oliviertassinari
Copy link
Member

oliviertassinari commented Jun 21, 2018

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());
  });
});

@kallebornemark
Copy link
Contributor

kallebornemark commented Jul 5, 2018

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

@oliviertassinari
Copy link
Member

unwrap() seems to be missing from the type definitions

@kallebornemark Right, we need to add it 👍

@mbrookes
Copy link
Member

mbrookes commented Jul 5, 2018

@kallebornemark Care to give it a go?

@mynameistechno
Copy link
Author

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());
  });
});

@CristianoYL
Copy link

CristianoYL commented Aug 15, 2018

@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.

@stocky37
Copy link

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.

@IssueHuntBot
Copy link

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

@amalv
Copy link

amalv commented Feb 15, 2019

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.

@eps1lon
Copy link
Member

eps1lon commented Jun 25, 2019

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.

@mynameistechno
Copy link
Author

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.

@oliviertassinari
Copy link
Member

👍 for closing, unless @eps1lon wants to update the documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Improvements or additions to the documentation test
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants