How to test a TextField Material-UI element with React Jest?

25,688

Solution 1

I have been writing test for few days using mocha, enzyme and chai. The problem that comes with testing material-ui is these are inturn react component so they cannot be tested as you test regular html elements.

You can find out what property change by printing the whole component, like

console.log(wrapper.find('TextField').debug());

This will print the whole element for you, you can notice that the TestField has value prop which is what you are suppose to check because this prop is what decided the value in the TextField

So the code will go like this:

describe('Initial test', () => {
  test('Shows error message when input search is empty.', () => {
    const { wrapper, props } = setup();
    expect(wrapper.find(TextField).props().value).to.equal('');
  });
}

This is how I have been testing the TextField component.

Hope you find it helpful.

Solution 2

Please, if someone has a better solution answer my question. After some attempts, I figured out how to test some material UI components. Basically, we need to find the native html elements (input, button, etc) inside the material UI components via enzyme find. I also realized that "shallow", only do a one level deep search, as @Andreas Köberle said in comments below. To force a deep search in the DOM tree we need to use enzyme "mount". http://airbnb.io/enzyme/docs/api/ReactWrapper/mount.html

Here is my new test code.

import React from 'react';
import { shallow, mount } from 'enzyme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { search } from '../sagas/search';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import Toggle from 'material-ui/Toggle';
import InputSearch from '../components/InputSearch/InputSearch';


const resetSearchMock = jest.fn();
const searchBooksMock = jest.fn();
const toggleResultsOpacityMock = jest.fn();
const muiTheme = getMuiTheme();
const props = {
  keyword: '',
  resetSearch: resetSearchMock,
  searchBooks: searchBooksMock,
  toggleResultsOpacity: toggleResultsOpacityMock,
  firstSearch: true,
};

const setup = () => {
  const wrapper = mount(
    <InputSearch {...props} />,
    {
      context: {muiTheme},
      childContextTypes: {muiTheme: React.PropTypes.object}
    }
  );

  return {
    props,
    wrapper,
  };
};

const { wrapper } = setup();
const textFieldMUI = wrapper.find(TextField);
const toggleAuthor = wrapper.find(Toggle).find('input#author');
const toggleTitle = wrapper.find(Toggle).find('input#title');
const button = wrapper.find(RaisedButton).find('button');

describe ('Initial test, validate fields', () => {  
  test('TextField component should exists.', () => {
    expect(textFieldMUI).toBeDefined();
  });

  test('Shows an error message when input search is empty and the search button is clicked.', () => {
    const { props } = setup();
    props.keyword = '';

    const wrapper = mount(
      <InputSearch {...props} />,
      {
        context: {muiTheme},
        childContextTypes: {muiTheme: React.PropTypes.object}
      }
    );

    button.simulate('click');
    expect(textFieldMUI.props().errorText).toEqual('This field is required');
  });

  test('Shows an error message when both "author" and "title" toggles are off and the search button is clicked.', () => {
    toggleTitle.simulate('click');
    button.simulate('click');
    expect(textFieldMUI.props().errorText).toEqual('Select at least one filter (Title or Author)');
  });

});

Solution 3

Enzyme shallow renders only one level deep, so in your case only MuiThemeProvider and InputSearch are rendered. You can use Jest snapshot feature to see what was rendered inside wrapper. You can use dive to force Enzyme to render the content of a component:

expect(wrapper.('InputSearch').dive().find(TextField).getValue()).toEqual('');

or you dont wrap the component with MuiThemeProvider and render InputSearch directly. You only need to add the styles prop. Now InputSearch is the top level component and Enzyme will render its content.

const setup = () => {
  const props = {
    keyword: '',
    resetSearch: resetSearchMock,
    searchBooks: searchBooksMock,
    toggleResultsOpacity: toggleResultsOpacityMock,
    firstSearch: true,
    styles: {textfield: {fontSize:10}}
  };

  const wrapper = shallow(<InputSearch {...props} />);

  return {
    props,
    wrapper,
  };
};
Share:
25,688
Pablo Darde
Author by

Pablo Darde

Specialist in frontend technologies such as ReactJS, Redux, Jest, Vanilla JavaScript (es5, es6, es7), CSS3, Styled components, webpack, and others. Advanced knowledge in backend with NodeJS, express (RestFul API), MongoDB. Also, have skills with Backend with NodeJS. I work with Git, Circle Ci, and Docker. I'm an agile developer formed with XP principles. I have a bachelor's degree in Software Engineering and I'm currently taking a postgraduate course in Data Science and Artificial Intelligence. Visit my LinkedIn

Updated on June 25, 2020

Comments

  • Pablo Darde
    Pablo Darde almost 4 years

    I built up a component with React and Material-UI. I'm using React and Redux.

    my index.jsx looks like this:

    import React from 'react';
    import { render } from 'react-dom';
    import { Provider } from 'react-redux';
    import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
    import configureStore from '../store/configureStore';
    import Routes from '../routes/routes';
    import '../styles/main.less';
    
    const store = configureStore();
    render(
      <Provider store={store}>
        <MuiThemeProvider>
          <Routes />
        </MuiThemeProvider>
      </Provider>,
      document.getElementById('app'),
    );
    

    My component InputSearch looks like this:

    import React, { PropTypes, Component } from 'react';
    import TextField from 'material-ui/TextField';
    
    class InputSearch extends Component {
      ...
    
      render() {
        return (
          ...
          <TextField
            defaultValue={this.props.keyword}
            ref={(input) => { this.input = input; }}
            autoFocus
            hintText='Type a keyword'
            errorText={this.state.errorText}
            floatingLabelText='Search for keyword'
            style={styles.textField}
          />
        );
      }
    }
    
    InputSearch.propTypes = {
      keyword: PropTypes.string.isRequired,
      resetSearch: PropTypes.func.isRequired,
      searchBooks: PropTypes.func.isRequired,
      toggleResultsOpacity: PropTypes.func.isRequired,
      firstSearch: PropTypes.bool.isRequired,
    };
    
    export default InputSearch;
    

    I'm using Airbnb Enzyme and Jest to build unit tests. My test to the InputSearch component looks like this:

    import React from 'react';
    import { shallow, mount } from 'enzyme';
    import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
    import TextField from 'material-ui/TextField';
    import InputSearch from '../components/InputSearch/InputSearch';
    
    const resetSearchMock = jest.fn();
    const searchBooksMock = jest.fn();
    const toggleResultsOpacityMock = jest.fn();
    
    const setup = () => {
      const props = {
        keyword: '',
        resetSearch: resetSearchMock,
        searchBooks: searchBooksMock,
        toggleResultsOpacity: toggleResultsOpacityMock,
        firstSearch: true,
      };
    
      const wrapper = shallow(<MuiThemeProvider><InputSearch {...props} /></MuiThemeProvider>);
    
      return {
        props,
        wrapper,
      };
    };
    
    describe('Initial test', () => {
      test('Shows error message when input search is empty.', () => {
        const { wrapper, props } = setup();
        expect(wrapper.find(TextField).getValue()).toEqual('');
      });
    }
    

    However, I'm getting the following error:

    TypeError: wrapper.find(...).getValue is not a function

    Can anyone help me reach the right way to check the value of the Material UI TextField, please?

  • Pablo Darde
    Pablo Darde about 7 years
    Hi @Andreas Köberle, thanks for your answer. I knew that "shallow" renders only one level deep, however, I tried with enzyme "mount" as well and it not works. I tried your suggestion with "dive" but this not solve. I also tried removing the "MuiThemeProvider" but not works too. Can you please try code a sample with material ui and TextField? Appreciate your help.
  • Andreas Köberle
    Andreas Köberle about 7 years
    Ok, so I cant find getValue in the enzyme docs. Maybe you are looking for prop('value') instead?
  • Pablo Darde
    Pablo Darde about 7 years
    Hey @Andreas Köberle, "getValue" is a material UI built in method to get the "TextField" component value.
  • Pablo Darde
    Pablo Darde about 7 years
    Hi @Farhaan Bukhsh, it is a good tip the "debug" function. However, my TextField component has not a "value" property, it only has a "defaultValue" property. How can I simulate a "change" event passing a new value and test it?
  • Farhaan Bukhsh
    Farhaan Bukhsh about 7 years
    Hey , you can replace the value to defaultValue if that is the prop you are having. Also I will suggest you to update material-ui. Now to simulate a change event it is very straight forward. wrapper.find('TextField') .find('input') .simulate('change', { target: { value: "Farhaan" } } ) , this will put Farhaan in the TextField now you can easily check the value.
  • JATIN KUMAR NAYAK
    JATIN KUMAR NAYAK almost 4 years
    The same tweak works for Material <select /> as well.
  • ysf
    ysf almost 4 years
    While this code may resolve the OP's issue, it is best to include an explanation as to how your code addresses the OP's issue. In this way, future visitors can learn from your post, and apply it to their own code. SO is not a coding service, but a resource for knowledge. Also, high quality, complete answers are more likely to be upvoted. These features, along with the requirement that all posts are self-contained, are some of the strengths of SO as a platform, that differentiates it from forums. You can edit to add additional info &/or to supplement your explanations with source documentation.