Material-UI withStyles components wont render when testing with Enzyme + Jest

Viewed 1031

I am trying to test a component. The test worked without styling, but now that I've styled it with Material-UI, the test no longer works. It seems the component, if styled with Material-UI will only render as [object Object] (undefined).

I have a form I'm exporting as: export default withStyles(styles)(ShuttleForm);

But I can test the component ShuttleForm, or any component that uses it now that I'm exporting it withStyles.

How can I test a styled material-ui component? I've looked at the material-ui docs and I've used their createShallow but it hasn't made a difference.

This is my test file:

import React from 'react';
import { createShallow } from '@material-ui/core/test-utils';
import { AddShuttlePage } from '../../components/AddShuttlePage';
import shuttles from '../fixtures/shuttles';

let addShuttle, history, shallow, wrapper;

beforeEach(() => {
  addShuttle = jest.fn();
  history = { push: jest.fn() };
  shallow = createShallow();
  wrapper = shallow(<AddShuttlePage addShuttle={addShuttle} history={history} />)
});

test('should render AddShuttlePage correctly', () => {
  expect(wrapper).toMatchSnapshot();
});

test('should handle onSubmit', () => {
  wrapper.find('ShuttleForm').prop('onSubmit')(shuttles[1]);
  expect(history.push).toHaveBeenLastCalledWith('/');
  expect(addShuttle).toHaveBeenLastCalledWith(shuttles[1]);
});

It keeps crashing at wrapper.find('ShuttleForm').prop... with a `Method “props” is only meant to be run on a single node. 0 found instead.

My component:

import React from 'react';
import { connect } from 'react-redux';
import ShuttleForm from './ShuttleForm';
import { addShuttle } from '../actions/shuttles';

export class AddShuttlePage extends React.Component {
  onSubmit = (shuttle) => {
    this.props.addShuttle(shuttle);
    this.props.history.push('/');
  };
  render() {
    return (
      <div>
        <h1>Add Shuttle</h1>
        <ShuttleForm
          onSubmit={this.onSubmit}
        />
      </div>
    );
  }
}

const mapDispatchToProps = (dispatch) => ({
  addShuttle: (shuttle) => dispatch(addShuttle(shuttle))
});

export default connect(undefined, mapDispatchToProps)(AddShuttlePage);

The component that is causing problems:

import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import 'react-dates/initialize';
import 'date-fns';
import DateFnsUtils from '@date-io/date-fns';
import { MuiPickersUtilsProvider, 
         KeyboardTimePicker,
         KeyboardDatePicker 
       } from '@material-ui/pickers';
import { withStyles } from '@material-ui/core/styles';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';

const styles = theme => ({
  root: {
    display: 'flex',
    flexWrap: 'wrap',
  },
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120,
  },
  selectEmpty: {
    marginTop: theme.spacing(2),
  },
  button: {
    margin: theme.spacing(1),
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
    width: 200,
  },
});

export class ShuttleForm extends React.Component {
  constructor(props) {
    super(props);

    this.state ={
      origin: props.shuttle ? props.shuttle.origin : '',
      destination: props.shuttle ? props.shuttle.destination : '',
      date: props.shuttle ? moment(props.shuttle.date) : moment(),
      time: props.shuttle ? props.shuttle.time : moment(),
      spots: props.shuttle ? props.shuttle.spots : '',
      cost: props.shuttle ? props.shuttle.cost : '',
      error: ''
    }
  }
  onOriginChange = (e) => {
    const origin = e.target.value;
    this.setState(() => ({ origin }));
  };
  onDestinationChange = (e) => {
    const destination = e.target.value;
    this.setState(() => ({ destination }));
  };
  onDateChange = (date) => {
    if (date) {
      this.setState(() => ({ date }));
    }
  };
  onFocusChange = ({ focused }) => {
    this.setState(() => ({ calendarFocused: focused }));
  };
  onTimeChange = (time) => {
    console.log('time',time);
    if (time) {
      this.setState(() => ({ time }));
    }
  };
  onSpotsChange = (e) => {
    const spots = e.target.value;
    this.setState(() => ({ spots }));
  };
  onCostChange = (e) => {
    const cost = e.target.value;
    this.setState(() => ({ cost }));
  };

  onSubmit = (e) => {
    e.preventDefault();

    if (!this.state.origin || !this.state.destination) {
      this.setState(() => ({ error: 'Please provide origin and destination.' }));
    } else {
      this.setState(() => ({ error: '' }));
      this.props.onSubmit({
        origin: this.state.origin,
        destination: this.state.destination,
        date: this.state.date.valueOf(),
        time: this.state.time.valueOf(),
        spots: e.target.elements.spots.value.trim(),
        cost: e.target.elements.cost.value.trim(),
      });
    }
  }

  render() {
    const { classes } = this.props;
    return (
      <MuiPickersUtilsProvider utils={DateFnsUtils}>
        <div>
          {this.state.error && <p>{this.state.error}</p>}
          <form className={classes.root} onSubmit={this.onSubmit}>
            <FormControl className={classes.formControl}>
              ***Form is here but I have removed it for brevity***
            <Button variant="contained" className={classes.button} type="submit">Add Shuttle</Button>
          </form>
        </div>
      </MuiPickersUtilsProvider>
    );
  }
}

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

export default withStyles(styles)(ShuttleForm);
1 Answers

Since you are wrapping the component in an HOC, you'll need to dive it in order to access your inner component beyond the HOC. This can be set when calling createShallow:

beforeEach(() => {
  addShuttle = jest.fn();
  history = { push: jest.fn() };
  shallow = createShallow({ dive: true });
  wrapper = shallow(<AddShuttlePage addShuttle={addShuttle} history={history} />)
});
Related