I have a component in react that renders another component based on the type using Enum properties. I am testing if appropriate component is rendered by mocking various types using Jest and Enzyme. I am new to react and Jest and I am having trouble with my test cases. Please can someone help me resolve this?
React Component:
const getComponent = {
'image': ImageComp,
'video': VideoComp,
'other': DocumentComp
}
const MediaDisplay = (props) => {
let { assetInfo } = props;
let source = assetInfo.assetUrl;
const PreviewComponent = getComponent[assetInfo.type];
return ( <div>
{source && <PreviewComponent assetInfo={assetInfo} />}
</div>
);
}
Unit Test:
import React from 'react';
import MediaDisplay from './../MediaDisplay';
import Enzyme, { shallow ,mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ImageComp from './../ImageComp';
import { mockAssetInfo } from '../../mocks/index';
Enzyme.configure({ adapter: new Adapter() });
describe('renders a PreviewComponent', () => {
it("wraps a ImageComp component", () => {
const component = shallow(<MediaDisplay assetInfo={mockAssetInfo} />);
expect(component.find(ImageComp)).toHaveLength(1);
});
});
MockAssetInfo:
export const mockAssetInfo = {
url: "https://example.com/image002.jpg",
name: "example",
type: "image",
thumb: "https://example.com?image0011.jpgch_ck=1212334354",
isAssetPublished:true
}
The error,
expect(received).toHaveLength(expected)
Expected length: 1
Received length: 0
Received object: {}
mockAssetInfo object does not have the key assetUrl. So, PreviewComponent is not rendered as source is undefined in the line
{source && <PreviewComponent assetInfo={assetInfo} />}
Related
I am using Jest with Enzyme for unit testing. I have a component that renders components based on the media type. Under unit testing, I am checking if the appropriate component has been rendered.
My Component
const getComponent = {
'image': ImageComp,
'video': VideoComp,
'other': DocumentComp
}
const MediaDisplay = (props) => {
let { assetInfo } = props;
let source = assetInfo.assetUrl;
const PreviewComponent = getComponent[assetInfo.type];
return ( <div>
{source && <PreviewComponent assetInfo={assetInfo} />}
</div>
);
}
In unit testing,
import React from 'react';
import MediaDisplay from './../MediaDisplay';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
describe('<MediaDisplay/>', () => {
it('should render Image component when asset type is image', () => {
const mockAssetInfo = {
assetUrl:'https://example.com/image001.jpg',
type:'image'
};
const component = mount(<MediaDisplay assetInfo={mockAssetInfo} />);
expect(component).toMatchSnapshot();
});
});
I don't think I am writing the test case correctly. Can someone help me write this test case?
P.S - I have a separate test case inside the image component to check if image is rendered, where I am checking if the tag has length.
Thanks a ton in advance.
I think your test is not doing what is suppose to test, because your case is telling that it should render Image component when asset type is image but you're just checking that the component is matching the snapshot, if you're using mount you should be able to see the content of the child components, so for example if your PreviewComponent displays different things depending on the props passed you could test those behaviors. Or if you want to check just that the assetInfo.type is 'image' you can always do:
it("contains an assetInfo image type", () => {
expect(component.prop("assetInfo").type).toEqual(mockAssetInfo.type);
});
And if you only want to check that the child is present and renders successfully you could use shallow instead mount and do this:
import PreviewComponent from "your-component-route"
describe("renders a PreviewComponent", () => {
beforeAll(() => {
const mockAssetInfo = {
assetUrl:'https://example.com/image001.jpg',
type:'image'
};
const component = shallow(<MediaDisplay assetInfo={mockAssetInfo} />);
});
it("wraps a PreviewComponent component", () => {
expect(component.find(PreviewComponent).length).toBe(1);
});
});
I am using Enzyme tests within Create-React-App. In shallow rendering it works fine, but mount rendering throws error:
TypeError: Cannot read property 'favorites' of undefined
Test file looks like this:
import React, { Component } from "react";
import configureMockStore from "redux-mock-store";
import { shallow, mount } from "enzyme";
import { Provider } from "react-redux";
import Favorites from "../Landing/Favorites";
const mockStore = configureMockStore();
const store = mockStore({});
function setup() {
const props = {
favorites: 42
};
const wrapper = mount(
<Provider store={store}>
<Favorites {...props} />
</Provider>
);
return {
props,
wrapper
};
}
describe("Favorites component", () => {
const { wrapper } = setup();
it("should render list of favorites cards", () => {
expect(wrapper.prop("favorites")).toEqual(42);
});
});
Why did it happen?
.prop works different in mount and shallow. You can check the docs.
http://airbnb.io/enzyme/docs/api/ReactWrapper/prop.html
http://airbnb.io/enzyme/docs/api/ShallowWrapper/prop.html
When using mount, you can directly render Favorites component.
mount(<Favorites {...props} />)
I have component in React which I'm trying to test with Jest, unfortunately test do not pass.
The component code:
import React, {Component} from 'react';
import ProductItem from '../ProductItem/ProductItem';
import AppBar from "#material-ui/core/es/AppBar/AppBar";
import Tabs from "#material-ui/core/es/Tabs/Tabs";
import Tab from "#material-ui/core/es/Tab/Tab";
import {connect} from 'react-redux';
class ProductsTabsWidget extends Component {
state = {
value: 0
}
renderTabs = () => {
return this.props.tabs.map((item, index) => {
return item.products.length > 0 ? (<Tab key={index} label={item.title}/>) : false;
})
}
handleChange = (event, value) => {
this.setState({value});
};
renderConentActiveTab = () => {
if (this.props.tabs[this.state.value]) {
return this.props.tabs[this.state.value].products.map((productIndex) => {
return (<ProductItem key={productIndex} {...this.props.products[productIndex]} />);
});
}
}
render() {
let tabs = null;
let content = null;
if (this.props.tabs) {
tabs = this.renderTabs();
content = this.renderConentActiveTab();
}
return (
<div>
<AppBar position="static" color="default">
<Tabs
value={this.state.value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
centered
scrollButtons="auto"
>
{tabs}
</Tabs>
</AppBar>
<div className="productWidget">
<div className="wrapper">
{content}
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
products: state.product.products,
}
}
export default connect(mapStateToProps)(ProductsTabsWidget);
I have tried to write proper test for this component, the code is below:
import React from 'react';
import {configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ProductsTabsWidget from "./ProductsTabsWidget";
configure({adapter: new Adapter()});
describe('ProductsTabsWidget - component', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<ProductsTabsWidget/>);
});
it('renders with minimum props without exploding', () => {
wrapper.setProps({
tabs: [],
products:[]
});
expect(wrapper).toHaveLength(1);
});
})
But when I'm running test I am getting error:
Test suite failed to run
F:\PRACA\reactiveShop\node_modules\#material-ui\core\es\AppBar\AppBar.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import _extends from "#babel/runtime/helpers/builtin/extends";
^^^^^^
SyntaxError: Unexpected token import
at new Script (vm.js:51:7)
at Object.<anonymous> (src/components/product/ProductsTabsWidget/ProductsTabsWidget.js:3:15)
I have tried testing with shallow, mount, render but it did not help. What am I missing?
My application is created on create-react-app.
It's something different when you're using #material-ui.
You've to use #material-ui's Built-in API(s). Such as createMount, createShallow, createRender in order to use enzyme's shallow, mount & render.
These APIs are built on top of enzyme, so you can't use enzyme directly for testing #material-ui.
Example of Shallow Rendering with #material-ui
import { createShallow } from '#material-ui/core/test-utils';
describe('<MyComponent />', () => {
let shallow;
before(() => {
shallow = createShallow();
});
it('should work', () => {
const wrapper = shallow(<MyComponent />);
});
});
Reference: Official Docs of #material-ui
Following is a humble attempt to provide a more complete answer from create-react-app and #material-ui perspective.
1. Create setupTests.js directly in src folder and paste the following code.
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
2. The following is react stateless component which uses material-ui components.
import React from "react";
import TextField from "#material-ui/core/TextField";
const SearchField = props => (
<TextField InputProps={{ disableUnderline: true }} fullWidth
placeholder={props.placeholder}
onChange={props.onChange}
/>
);
export default SearchField;
Note that in the above component, the component expects parent component to pass the props for placeholder and onChange() event handler
3. Coming to the test case for for the above component we can write either in a way material-ui suggests or in a pure enzyme style. Both will work.
Pure Enzyme Style
import React from "react";
import { mount } from "enzyme";
import TextField from "#material-ui/core/TextField";
import SearchField from "../SearchField";
describe("SearchField Enzyme mount() ", () => {
const fieldProps = {
placeholder: "A placeholder",
onChange: jest.fn()
};
const Composition = props => {
return <SearchField {...fieldProps} />;
};
it("renders a <TextField/> component with expected props", () => {
const wrapper = mount(<Composition />);
expect(wrapper.childAt(0).props().placeholder).toEqual("A placeholder");
expect(wrapper.childAt(0).props().onChange).toBeDefined();
});
it("should trigger onChange on <SearchField/> on key press", () => {
const wrapper = mount(<Composition />);
wrapper.find("input").simulate("change");
expect(fieldProps.onChange).toHaveBeenCalled();
});
it("should render <TextField />", () => {
const wrapper = mount(<Composition />);
expect(wrapper.find(TextField)).toHaveLength(1);
expect(wrapper.find(TextField).props().InputProps.disableUnderline).toBe(
true
);
});
});
Material UI style
import React from "react";
import { createMount } from "#material-ui/core/test-utils";
import TextField from "#material-ui/core/TextField";
import SearchField from "../SearchField";
describe("SearchField", () => {
let mount;
const fieldProps = {
placeholder: "A placeholder",
onChange: jest.fn()
};
beforeEach(() => {
mount = createMount();
});
afterEach(() => {
mount.cleanUp();
});
it("renders a <TextField/> component with expected props", () => {
const wrapper = mount(<SearchField {...fieldProps} />);
expect(wrapper.props().placeholder).toEqual("A placeholder");
expect(wrapper.props().onChange).toBeDefined();
});
it("should trigger onChange on <SearchField/> on key press", () => {
const wrapper = mount(<SearchField {...fieldProps} />);
wrapper.find("input").simulate("change");
expect(fieldProps.onChange).toHaveBeenCalled();
});
});
5. The error you are getting is due to the fact that babel is not getting a chance to process your file. The create-react-app expects you to run tests like yarn run test and not like jest your/test/file.js. If you use latter babel won't be employed.
If you want to use jest to run the file you will have to write a jest.config.js file or configure jest in package.json file to use babel-jest + other babel dependencies to transpile your code before jest tries to execute tests.
I was in the same boat yesterday as I tried to use #material-ui for the first time and came here to get a more complete answer.
Something like this worked for me:
import {createMount} from '#material-ui/core/test-utils';
const WrappedComponent = () =>
<MUIThemeStuffEtc>
<MyComponent />
</MUIThemeStuffEtc>
const render = createMount();
const wrapper = render(<WrappedComponent />);
const state = wrapper.find(MyComponent).instance().wrappedInstance.state
New to React, but not to test applications.
I'd like to make sure every time a component throws a error the ErrorBoundary message is displayed. If you don't know what I mean by ErrorBoundary here is a link.
I'm using Mocha + Chai + Enzyme.
Let's say we need to test React counter example using the following test configuration.
Test Configuration
// DOM
import jsdom from 'jsdom';
const {JSDOM} = jsdom;
const {document} = (new JSDOM('<!doctype html><html><body></body></html>')).window;
global.document = document;
global.window = document.defaultView;
global.navigator = global.window.navigator;
// Enzyme
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
// Chai
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
chai.use(chaiEnzyme());
UPDATE 1 - Some later thoughts
After reading this conversation about the best testing approach for connected components (which touches similar issues) I know I don't have to worry about componentDidCatch catching the error. React is tested enough and that ensures that whenever a error is thrown it will be caught.
Therefore there are only test two tests:
1: Make sure ErrorBoundary displays the message if there's any error
// error_boundary_test.js
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import ErrorBoundary from './some/path/error_boundary';
describe('Error Boundary', ()=>{
it('generates a error message when an error is caught', ()=>{
const component = shallow(<ErrorBoundary />);
component.setState({
error: 'error name',
errorInfo: 'error info'
});
expect(component).to.contain.text('Something went wrong.');
});
});
2: Make sure component is wrapped inside the ErrorBoundary (in the React counter example is <App />, which is misleading. The idea is to do that on the closest parent component).
Notes: 1) it needs to be done on the parent component, 2) I'm assuming children are simple components, not containers, as it might need more config.
Further thoughts: this test could be better written using parent instead of descendents...
// error_boundary_test.js
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import App from './some/path/app';
describe('App', ()=>{
it('wraps children in ErrorBoundary', ()=>{
const component = mount(<App />);
expect(component).to.have.descendants(ErrorBoundary);
});
To test ErrorBoundary component using React Testing Library
const Child = () => {
throw new Error()
}
describe('Error Boundary', () => {
it(`should render error boundary component when there is an error`, () => {
const { getByText } = renderProviders(
<ErrorBoundary>
<Child />
</ErrorBoundary>
)
const errorMessage = getByText('something went wrong')
expect(errorMessage).toBeDefined()
})
})
renderProviders
import { render } from '#testing-library/react'
const renderProviders = (ui: React.ReactElement) => render(ui, {})
This was my attempt without setting component state:
ErrorBoundary:
import React, { Component } from 'react';
import ErroredContentPresentation from './ErroredContentPresentation';
class ContentPresentationErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
render() {
return this.state.hasError ? <ErroredContentPresentation /> : this.props.children;
}
}
export const withErrorBoundary = WrappedComponent =>
props => <ContentPresentationErrorBoundary>
<WrappedComponent {...props}/>
</ContentPresentationErrorBoundary>;
And the test:
it('Renders ErroredContentPresentation Fallback if error ', ()=>{
const wrappedComponent = props => {
throw new Error('Errored!');
};
const component = withErrorBoundary( wrappedComponent )(props);
expect(mount(component).html()).toEqual(shallow(<ErroredContentPresentation/>).html());
});
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?
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.
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)');
});
});
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,
};
};
const event = { 'target': { 'value': 'No' } };
wrapper.find('#selectBox').prop('onChange').call(null, event);