How to test a TextField Material-UI element with React Jest?
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,
};
};
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, 2020Comments
-
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 about 7 yearsHi @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 about 7 yearsOk, so I cant find
getValue
in the enzyme docs. Maybe you are looking forprop('value')
instead? -
Pablo Darde about 7 yearsHey @Andreas Köberle, "getValue" is a material UI built in method to get the "TextField" component value.
-
Pablo Darde about 7 yearsHi @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 about 7 yearsHey , you can replace the
value
todefaultValue
if that is theprop
you are having. Also I will suggest you to updatematerial-ui
. Now tosimulate
achange
event it is very straight forward.wrapper.find('TextField') .find('input') .simulate('change', { target: { value: "Farhaan" } } )
, this will putFarhaan
in theTextField
now you can easily check the value. -
JATIN KUMAR NAYAK almost 4 yearsThe same tweak works for Material
<select />
as well. -
ysf almost 4 yearsWhile 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.