So I started with unit testing React components composed of Material-UI components using Jest and Enzyme setup. So far every event simulation was working fine until I encountered Select component from Material-UI. (More info below)
Project was bootstrapped using create-react-app and utilized material-ui-next.
Dependencies Version
Problem
I have a pure functional component named FiltersDesktop composed of Material-UI form fields. Three of which are Select components and others are textfield and date picker from material-ui.
UI Code
<Collapse in={props.visible} className={'filters-container-desk'}>
<Grid container classes={{ typeContainer: "filter-container" }} id={'container-desk'}>
<Grid item lg={2} xl={2}>
<FormControl fullWidth={true}>
<InputLabel htmlFor="sources">Sources</InputLabel>
<Select
open
inputProps={{ name: 'sources-desk', id: 'sources-desk' }}
value={props.filters.source || ''}
onChange={(event) => props.updateFilter({ source: event.target.value })}
>
<MenuItem value=''>
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item lg={2} xl={2}>
...
</Grid>
... // More Grid components of similar structure as above
</Grid>
</Collapse>
When I try to simulate change event on textfield component it works fine. Following is the code I wrote for testing a textfield:
Test Code for TextField
const props = {
updateFilter: jest.fn()
};
test(`updateFilter should get called on simulating text changes in domain`, () => {
const component = mount(<FiltersDesktop {...this.props} />);
component.find('#domain-desk').last().simulate('change', { target: { value: 'scoopwhoop.com' } });
expect(props.updateFilter).toHaveBeenCalled();
});
But something similar doesn't work for Select component. However the update function gets called when I actually interact with the Select field via interface. Following is the test code I wrote for testing Select:
Test Code for Select
const props = {
updateFilter: jest.fn()
};
test(`updateFilter should get called on simulating text changes in sources`, () => {
const component = mount(<FiltersDesktop {...this.props} />);
// Did not work
component.find('#sources-desk').last().simulate('change', { target: { value: 20 } });
// Did not work
component.find('Select').first().simulate('change', { taget: { value: 20 } });
// Did not work
component.find('#sources-desk').forEach(element => element.simulate('change', { taget: { value: 20 } }))
expect(props.updateFilter).toHaveBeenCalled();
});
For some reasons find method always returned me more than 1 elements in all above cases hence I resorted to use first, last and forEach in appropriate cases.
Kindly help my figure out the reason behind the change event not being triggered on Select component on simulating it.
Rest be assured that I have spent atleast a week reading issues on Github, trying to implement the solutions and test guides for Jest and Enzyme. I am sure my test setup is fine as rest of the cases are working fine.
If you want to look at the source code precisely then here is the link to the repository. Pull requests on repository are also appreciated. If you are on repository, then don't forget to switch to react-material branch.
P.S - Don't laugh as I just have recently started with React :P
I cloned your branch and made the test works, but I figured out something intersting: the simulate method works different in a ShallowWrapper and ReactWrapper, that is the simulate works different when using shallow and using mount.
First, take a look at this question: When should you use render and shallow in Enzyme / React tests?
The most suitable for this kind of test should be shallow() and not mount(), because you are testing this component as a unit.
Test working with mount:
test("updateFilter should get called on simulating text changes in sources", () => {
const component = mount(<FiltersDesktop {...props} />);
component
.find(Select)
.at(0)
.props()
.onChange({ target: { value: 20 } });
expect(props.updateFilter).toHaveBeenCalled();
});
Test working with shallow:
test("updateFilter should get called on simulating text changes in sources", () => {
const wrapper = shallow(<FiltersDesktop {...props} />);
wrapper
.find(Select)
.at(0)
.simulate("change", { target: { value: 20 } });
expect(props.updateFilter).toHaveBeenCalled();
});
As you can see, in the second approach using shallow, I called the variable wrapper because it's a wrapper to the component, and not the component, furthermore you can notice that in the first approach with mount, the test only works when call the onChange directly, through the props.