Material-ui: withStyles break enzyme tests

Created on 22 Nov 2017  路  20Comments  路  Source: mui-org/material-ui

Seems like when using the withStyles HOC it'll actually break my Jest test. It creates children elements where it shouldn't.

Here is the code to reproduce it:

import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';

import withStyles from 'material-ui/styles/withStyles';
import LoadingWrapper from './LoadingWrapper';

const ReproBug = ({ loading, children }) => {
  if (loading) {
    return <span>Loading...</span>;
  }
  return children;
}
const ReproBugWithStyles = withStyles({})(ReproBug);


const children = <span>Dummy Children!</span>;

it('should not render children if loading is true', () => {
  const wrapper = shallow(
    <ReproBug loading={true}>{children}</ReproBug>
  );
  expect(wrapper.contains(children)).toEqual(false);
});

it('should not render children if loading is true', () => {
  const wrapper = shallow(
    // using ReproBugWithStyles instead of ReproBug
    <ReproBugWithStyles loading={true}>{children}</ReproBugWithStyles>
  );
  expect(wrapper.contains(children)).toEqual(false); //<--- this check fails, it is actually true
});

I use the create-react-test and simply run yarn test. (they use Jest and enzyme IIRC)

question test

Most helpful comment

I had some trouble getting my head around why shallow wasn't working for some tests when I started using withStyles, so I broke it down into a few self-contained examples at https://gist.github.com/a-h/21df0f432ae02a6dfed941debb0e5950

Once I did that, I realised that I needed to switch from shallow to mount since withStyles is a higher order component and 'wraps' the other component.

All 20 comments

I also notice this in "real world" usage beside the jest test. For example I use this kind of logic on my <LoadingWrapper> for my Apollo requests and Apollo set the loading and data (and some more) variable. So I could make a simple Wrapper which avoid loading the children elements as long as data isn't recieved.

<LoadingWrapper loading={loading}>
    <UseDataElement data={data} />
</LoadingWrapper>

With this bug the <UseDataElement> element is actually "rendered" (it is not) or at least triggered so I receive propTypes warning for missing data.

Mh I'll get similar errors when trying some of your intern testing code. For example when trying this:
https://github.com/mui-org/material-ui/blob/930937827c58f9468299e9760ac9f88e2b34ebcd/src/Divider/Divider.spec.js#L17-L20

I'll get this error:

Expected value to equal:
      "hr"
Received:
      "Divider"

Maybe I should use your testing scripts instead of the create-react-app ones.

@Skaronator I was having a ton of issues testing my application. Once I found Mui's docs on testing and used their Shallow and Mount I had no issues.

It creates children elements where it shouldn't

@Skaronator The issue isn't about the children elements but with the intermediary element, it's creating. The shallow() API of enzyme only render one level depth. Any higher-order component is going to obfuscate the children. You have different workarounds. I would encourage you to refer to the enzyme documentation. Still, here they are:

@codepressd how did you manage to get testing working? I keep getting issues like find isn't a function. I cannot target my clickable objects for the life of me.

@FahdW I'm using create-react-app and therefore Jest for the test. find() works fine for me but I use childAt(<int>) or at(<int>) most of the time. Here is a click test I'm using:

import React from 'react';
import ReactDOM from 'react-dom';
import { createShallow, getClasses } from 'material-ui/test-utils';

import AppDrawerElements from './AppDrawerElements';

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

  const TestIcon = () => <span>Not a Icon</span>;

  const URL = {
    display: {
      url: '/theUrl/with/more',
      name: 'Testing...',
      display: true,
      icon: <TestIcon />,
    },
  };

  beforeAll(() => {
    shallow = createShallow({ dive: true });
  });

  it('should fire onClose when click the close button', () => {
    const onCloseMock = jest.fn();
    const onClose = jest.fn(() => {
      onCloseMock();
    });

    const wrapper = shallow(<AppDrawerElements url={URL} onClose={onClose} />);

    wrapper
      .childAt(0)
      .childAt(0)
      .childAt(0)
      .simulate('click');
    expect(onCloseMock).toBeCalled();
  });
});

@Skaronator Thanks for this! How would you test if the function itself was contained in the component? I am guessing you call a spy, so far jest's spy has been underwhelming and I have yet to get it to work. I was going to send a spy onto my dispatch to make sure that some of the internal functions are firing off dispatch calls. But this is good to point me in the right direction.

@FahdW You might find it helpful to look at some of Material-UI's own tests.

@mbrookes Thanks will take a look at your source, hopefully find something to help me.

I had some trouble getting my head around why shallow wasn't working for some tests when I started using withStyles, so I broke it down into a few self-contained examples at https://gist.github.com/a-h/21df0f432ae02a6dfed941debb0e5950

Once I did that, I realised that I needed to switch from shallow to mount since withStyles is a higher order component and 'wraps' the other component.

I found that material-ui was not breaking my tests even when using withStyles and that this worked just fine using plain old enzyme:

import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

// CODE OMITTED HERE

it('renders title matches it with containsMatchingElement', () => {
     const wrapper = shallow(<ElfHeader />);
     const target = <Typography>Address Maven</Typography>;
     expect(wrapper.dive().containsMatchingElement(target)).toBe(true);
});

Here was the JSX I wanted to match:

<Typography variant="title" color="inherit" className={classes.flex}>
    Address Maven
</Typography>

And here was the messy code material-ui produced:

<WithStyles(Typography) variant="title" color="inherit" className="ElfHeader-flex-100">
       Address Maven
</WithStyles(Typography)>

Nevertheless, my test passed.

I say this only because this thread is a bit misleading for those who get errors and don't know their source. I guess my point is simply that in most cases, your tests should pass. Note this line from the docs: "You can take advantage of them if you so choose." Not that you have to use them, but use them if you find them convenient.

https://material-ui.com/guides/testing/

The shallow = createShallow({ dive: true }); totally made my day... 馃巻

Why this issue is closed?
None of those is working for me,
Still get displayed with
<WithStyles(Component)

Cannot go past through it...

I'm having the same issue as Matteo. When testing the output in Jest i get

For example if I want to see if a Drawer component is present I have to test for

Is there a way to get the test render to be the unwrapped component? or is the only way to test for the

It shouldn't be too long before we start/try using hooks over higher-order component internally for the styles. I'm not sure what is the implication for shallow rendering. It will most likely solve this class of issues.

I solved my issue, was an obvious fix. I was using the component name in quotes eg .find('Avatar') instead of importing it into the test and calling it directly .find(Avatar) - no quotes. That worked.

@jondbm Solved the same way

I am able to proceed with createShallow and it works fine for Components using WithStyles but I couldn't make it work with Components which needs redux.

Works:
import PropTypes from "prop-types";
import React, { Component } from "react";
import { connect } from "react-redux";
import { makeStyles, withStyles } from '@material-ui/core/styles';
import CircularProgress from '@material-ui/core/LinearProgress';

const useStyles = (theme) => ({
root: {
flexGrow: 1,
},
extendedIcon: {
marginRight: theme.spacing(1),
}
});

class LogoutSuccess extends Component {

render() {
    const { classes } = this.props;
    return (
        <p>
            <CircularProgress size={24} className={classes.extendedIcon} />
        </p>
    );
};

}

LogoutSuccess.propTypes = {
classes: PropTypes.object.isRequired,
};

export default (withStyles(useStyles)(LogoutSuccess));

import { Provider } from "react-redux";
import React from "react";
import Enzyme, { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import LogoutSuccess from "../../../src/client/components/logoutsuccess";
import sinon from "sinon";
import configureMockStore from "redux-mock-store";
import axios from "axios";
import thunk from "redux-thunk";
import * as mockData from '../mock';
import { createShallow } from '@material-ui/core/test-utils';

Enzyme.configure({ adapter: new Adapter() });

const getStore = props => {
const intialState = props;
const middlewares = [thunk.withExtraArgument(axios)];
const mockstore = configureMockStore(middlewares);
const store = mockstore(intialState);
return store;
};

describe("Home Page ", () => {
let sandbox;

beforeEach(() =>
sandbox = sinon.createSandbox()

);
afterEach(() => sandbox.restore());

it('renders title matches it with containsMatchingElement', () => {

const shallow = createShallow();
const wrapper = shallow(<LogoutSuccess />);    
 expect(wrapper.dive().find(CircularProgress)).toHaveLength(1);

});
});

Not working:
import PropTypes from "prop-types";
import React, { Component } from "react";
import { connect } from "react-redux";
import { makeStyles, withStyles } from '@material-ui/core/styles';
import CircularProgress from '@material-ui/core/LinearProgress';

const useStyles = (theme) => ({
root: {
flexGrow: 1,
},
extendedIcon: {
marginRight: theme.spacing(1),
}
});

class LogoutSuccess extends Component {

render() {
    const { classes } = this.props;
    return (
        <p>
            <CircularProgress size={24} className={classes.extendedIcon} />
        </p>
    );
};

}

const mapStateToProps = state => ({
countryCodes: state.formatbanner.countryCodes
});

LogoutSuccess.propTypes = {
classes: PropTypes.object.isRequired,
};

export default connect(mapStateToProps, null, null, { withRef: true })(withStyles(useStyles)(LogoutSuccess));

import { Provider } from "react-redux";
import React from "react";
import Enzyme, { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import LogoutSuccess from "../../../src/client/components/logoutsuccess";
import sinon from "sinon";
import configureMockStore from "redux-mock-store";
import axios from "axios";
import thunk from "redux-thunk";
import * as mockData from '../mock';
import { createShallow } from '@material-ui/core/test-utils';

Enzyme.configure({ adapter: new Adapter() });

const getStore = props => {
const intialState = props;
const middlewares = [thunk.withExtraArgument(axios)];
const mockstore = configureMockStore(middlewares);
const store = mockstore(intialState);
return store;
};

describe("Home Page ", () => {
let sandbox;

beforeEach(() =>
sandbox = sinon.createSandbox()

);
afterEach(() => sandbox.restore());

it('renders title matches it with containsMatchingElement', () => {

const shallow = createShallow();
const mockstore = configureMockStore();
const store = mockstore(mockData.data);    
const wrapper = shallow(<Provider store={store}><LogoutSuccess /></Provider>);    
 expect(wrapper.dive().find(CircularProgress)).toHaveLength(1);

});
});

@a-h @Skaronator @MatteoGioioso @jondbm Can someone help me if you have written test cases for components having dependent on redux?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mattmiddlesworth picture mattmiddlesworth  路  3Comments

chris-hinds picture chris-hinds  路  3Comments

mb-copart picture mb-copart  路  3Comments

newoga picture newoga  路  3Comments

reflog picture reflog  路  3Comments