Skip to content

Instantly share code, notes, and snippets.

@a-h
Last active December 31, 2023 09:07
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save a-h/21df0f432ae02a6dfed941debb0e5950 to your computer and use it in GitHub Desktop.
Save a-h/21df0f432ae02a6dfed941debb0e5950 to your computer and use it in GitHub Desktop.
Testing styled Material UI components with Enzyme
import React from 'react';
import { shallow } from 'enzyme';
const Item = text => <p>Item {text}</p>;
const Composition = ({ showB }) => (
<p>
<Item text="A" />
{showB && <Item text="B" />}
</p>);
describe('<Composition />', () => {
it('should render one item if showB is set to false', () => {
const wrapper = shallow(<Composition showB={false} />);
expect(wrapper.find(Item)).toHaveLength(1);
});
it('should render two items if showB is set to true', () => {
const wrapper = shallow(<Composition showB />);
expect(wrapper.find(Item)).toHaveLength(2);
});
});
import React from 'react';
import { shallow } from 'enzyme';
import { CircularProgress } from 'material-ui-next/Progress';
const Composition = () => (
<p>
<CircularProgress size={24} />
</p>);
describe('<Composition />', () => {
it('should render a CircularProgress', () => {
const wrapper = shallow(<Composition />);
expect(wrapper.find(CircularProgress)).toHaveLength(1);
});
});
import React from 'react';
import { shallow } from 'enzyme';
import { CircularProgress } from 'material-ui-next/Progress';
import { withStyles } from 'material-ui-next/styles';
import PropTypes from 'prop-types';
const style = {
buttonProgress: {
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
};
const Composer = ({
classes,
}) => (
<p>
<CircularProgress size={24} className={classes.buttonProgress} />
</p>);
Composer.propTypes = {
classes: PropTypes.object.isRequired,
};
const Composition = withStyles(style)(Composer);
describe('<Composition />', () => {
it('should render a styled CircularProgress', () => {
const wrapper = shallow(<Composition />);
// Note the use of dive() because Composition is now wrapped by the withStyles higher order component.
expect(wrapper.dive().find(CircularProgress)).toHaveLength(1);
});
});
import React from 'react';
import { CircularProgress } from 'material-ui-next/Progress';
import { createShallow } from 'material-ui-next/test-utils';
import { withStyles } from 'material-ui-next/styles';
import PropTypes from 'prop-types';
const style = {
buttonProgress: {
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
};
const Composer = ({
classes,
}) => (
<p>
<CircularProgress size={24} className={classes.buttonProgress} />
</p>);
Composer.propTypes = {
classes: PropTypes.object.isRequired,
};
const Composition = withStyles(style)(Composer);
describe('<Composition />', () => {
const shallow = createShallow();
it('should render CircularProgress', () => {
const wrapper = shallow(<Composition />);
// Still need to dive().
expect(wrapper.dive().find(CircularProgress)).toHaveLength(1);
});
});
import React from 'react';
import { CircularProgress } from 'material-ui-next/Progress';
import { mount } from 'enzyme';
import { withStyles } from 'material-ui-next/styles';
import PropTypes from 'prop-types';
const style = {
buttonProgress: {
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
};
const Composer = ({
classes,
}) => (
<div>
<CircularProgress size={24} className={classes.buttonProgress} />
</div>);
Composer.propTypes = {
classes: PropTypes.object.isRequired,
};
const Composition = withStyles(style)(Composer);
describe('<Composition />', () => {
it('should render CircularProgress', () => {
const wrapper = mount(<Composition />);
// No longer need to dive().
expect(wrapper.find(CircularProgress)).toHaveLength(1);
});
});
@clairecassan
Copy link

clairecassan commented Aug 1, 2020

Hi,
I have changed my code to wrap my components with 'withStyles' and all my unit tests are now failing. Your example 05-simple-material-withstyles-using-mount.test.js matches what I am doing in my test:

it("Tests changing the number of rows for add/remove/insert", () => {
  const wrapper = mount(<Graph id="graph" />);
  const editingTab = wrapper.find(EditingTab).first();
  expect(editingTab.exists()).toBe(true);
  expect(editingTab.state().editRowCount).toEqual(10); <<- Fails with ReactWrapper::state() can only be called on class components
  wrapper.instance().removeRows(5); <<- Fails with Cannot read property 'removeRows' of null

In your test above how would you get the state of the CircularProgress object if it was also a styled component? And how would you get the wrapper instance to have access to Composition's public API?

I got it to work by calling children.first() like that:

    const wrapper = mount(<Graph  id="graph" />);
    expect(wrapper.children()).toHaveLength(1);
    const graph= wrapper.children().first();

    let editingTab = wrapper.find(EditingTab).first();
    expect(editingTab.exists()).toBe(true);
    expect(editingTab.children()).toHaveLength(1);
    editingTab = editingTab.children().first();

    expect(editingTab.state().editRowCount).toEqual(10);
    graph.instance().removeRows(5);

But I have to manually edit 200+ unit tests, so I'm hoping I missed something simpler.

Thank you
Cl.

@a-h
Copy link
Author

a-h commented Aug 3, 2020

Hi, I'm not sure I'm going to be of great help here - I have only ever used Redux for state management and use stateless functional components to populate the props of a component. The props of each styled component are accessible (I put together an example here: https://github.com/a-h/styled-test - run npx jest to run the test) so deeper inspection isn't required.

Sounds like your code structure is already pretty fixed, but I'd consider restructuring to have stateless functional components to handle the presentation. Those components can be tested simply - e.g. "given a row count of 10, 10 rows are rendered", "when this button is clicked, the onClick prop is called". Then, I'd add a higher-order component that wraps the others. The tests here would be along the lines of "when this function is called, the prop passed to this component changes".

If you find a nice solution, I'd be interested to see it!

@clairecassan
Copy link

Thank you. I am testing button clicks, state and direct API calls, and I agree with you that I shouldn't test state and API to test what is rendered instead. Unfortunately I use react-Konva and the graph is quite complex, I didn't manage to test the rendering yet.

@lane-eb
Copy link

lane-eb commented Oct 9, 2020

Thanks very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment