8

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

  • React: 16.2.0
  • React-Dom: 16.2.0
  • React-Scripts: 1.1.1
  • Material UI: 1.0.0-beta.35
  • Jest: One that comes packed with setup (22.4.3)
  • Enzyme: 3.3.0
  • Enzyme Adapter React 16: 1.1.1

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

Himanshu Singh
  • 970
  • 2
  • 6
  • 18

1 Answers1

36

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.

Fabio Miranda
  • 551
  • 4
  • 7
  • 1
    Hi, thanks for the quick response. I went through the link you mentioned, quite a good explanation that I overlooked. Thanks for that. Also, I noted that instead of writing Select in quotes, I guess you actually put their a reference of Select from material-ui. I got it working this way. Was this intended ? and if so any reason why the find returns no shallow wrapper on supplying component as string. As in - .find('Select') – Himanshu Singh Apr 02 '18 at 03:52
  • 1
    Yes, it's a point that I forget to tell you too, I really prefer to use this approach, because string references are very easy to break, that why I avoid that. – Fabio Miranda Apr 02 '18 at 03:57
  • Hi thanks again, I went through the enzyme docs for shallow rendering and got my answer there. I will keep this one open till I am sure of certain things about shallow, will take at most 2 days. For those looking for correct solution, kindly refer this solution. Worked great for me and do take a look at the link mentioned. – Himanshu Singh Apr 02 '18 at 04:04
  • With the `mount()` example, be aware that you are calling the `onChange` function directly. This bypasses the opening of the Select and the verification that any entries actually exist inside it. You could pass a value of 3, when you only have 0-2 in the list and this will still work. MUI's own tests use `render()` and are of no help. With that said, we aren't really testing MUI or the browser, are we? https://github.com/enzymejs/enzyme/issues/1081#issuecomment-323866479 – Derek White Sep 27 '20 at 02:57