How to test React ErrorBoundary - reactjs

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());
});

Related

How Can I test react redux component by enzyme?

I have to do it a few simple React Enzyme tests. I want to check if component is rendered.
import React from 'react';
import { shallow } from 'enzyme';
import ConnSearch from './ConnSearch';
it('renders without errors', () => {
const component = shallow(<ConnSearch />);
console.log(component.debug());
});
I have results: Could not find "store" in the context of "Connect(ConnSearch)". Either wrap the root component in a , or pass a custom React context provider to a
nd the corresponding React context consumer to Connect(ConnSearch) in connect options.
My ConnSearch Component:
import React, { Component } from 'react';
import {fetchRoadDetails, fetchUserPoints} from "../../actions";
import {connect} from "react-redux";
import RoadTable from "../../components/RoadTable/RoadTable";
import RoadForm from "../../components/RoadTable/RoadForm";
import style from './ConnSearch.module.scss'
import {getPoints} from "../../reducers";
class ConnSearch extends Component {
constructor(props){
super(props);
this.state = {
};
}
componentDidMount() {
this.props.fetchUserPoints(this.props.userLogin);
}
render() {
return (
<div className={style.wrapper}>
<RoadForm />
<div className={style.tableWrapper} >
<RoadTable/>
</div>
</div>
);
}
}
const mapDispatchToProps=dispatch=>({
fetchRoadDetails:()=>dispatch(fetchRoadDetails()),
fetchUserPoints:(user)=>dispatch(fetchUserPoints(user)),
});
const mapStateToProps = (state) => {
return {
roads: state.road,
points:getPoints(state),
userLogin: state.userLogin,
};
};
export default connect(mapStateToProps,mapDispatchToProps)(ConnSearch);
How can I do this test ? I've never done that before.
Unfortunately, when I wrap it in a provider:
it('renders without errors', () => {
const component = shallow( <Provider store={store}><ConnSearch/></Provider>);
console.log(component.debug());
});
I got this:
console.log src/views/ConnectionSearch/ConnSearch.test.js:11
<ContextProvider value={{...}}>
<Connect(ConnSearch) />
</ContextProvider>
I want ConnSearch render structure.

Expect a component to be rendered

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} />}

Why enzyme tests did not work in React.js?

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} />)

React, Jest, Enzyme how to pass test App.js with store redux

Created a simple app using create-react-app then updated my App.js and added redux/store.
class App extends Component {
render() {
return (
<header className="header">
<h1>todos</h1>
</header>
);
}
}
function mapStateToProps(state) {
return {
data: state.data
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
then trying to test my App on App.test.js using Enzyme and Jest.
import React from 'react'
import Enzyme, { mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16';
import App from './App'
Enzyme.configure({ adapter: new Adapter() });
function setup() {
const props = {
addTodo: jest.fn()
}
const enzymeWrapper = mount(<App {...props} />)
return {
props,
enzymeWrapper
}
}
describe('components', () => {
describe('App', () => {
it('should render self and subcomponents', () => {
const { enzymeWrapper } = setup()
expect(enzymeWrapper.find('header')).toBe(true)
})
})
})
but throwing error: Invariant Violation: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(App)".
Any Ideas?
This is happening because you're trying to test the component without a <Provider> (which would normally make the store available to its sub-components).
What I normally do in this situation is test my component without the Redux connect binding. To do this, you can export the App component itself:
export class App extends Component // etc...
and then import that in the test file using the deconstructed assignment syntax:
import { App } from './App'
You can assume (hopefully... ;) ) that Redux and the React bindings have been properly tested by their creators, and spend your time on testing your own code instead.
There's a little more information about this in the Redux docs.

TypeError: dispatch is not a function when testing with react-create-app jest and enzyme

I'm trying to setup testing on a new project created with react-create-app. Which now seems to be using React 16 and Jest 3 (which supposedly had some breaking changes, or maybe that was enzime). I'm getting an error similar to this post TypeError: dispatch is not a function when I try to test a method using JEST
TypeError: dispatch is not a function
at App.componentDidMount (src/components/App.js:21:68)
import React from 'react';
import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import { App } from '../components/App';
import configureStore from '../state/store/configureStore';
window.store = configureStore({
slider: {
mainImageIndex: 0,
pageNum: 1,
perPage: 4,
},
});
const appTest = (
<Provider store={window.store}>
<App />
</Provider>
);
describe('App', () => {
it('should render without crashing', () => {
mount(appTest);
});
});
Originally I just tried to do this:
import React from 'react';
import { mount } from 'enzyme';
import { App } from '../components/App';
describe('App', () => {
it('should render without crashing', () => {
mount(<App />);
});
});
Which threw this error
Invariant Violation: Could not find "store" in either the context or props of "Connect(Form(SearchForm))". Either wrap the root component in a , or explicitly pass "store" as a prop
Code for App.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { searchPhotos } from '../state/actions/searchPhotos';
import { setMainImageIndex, setFirstPage } from '../state/actions/slider';
import Slider from './Slider';
import SearchForm from './SearchForm';
import Error from './Error';
import '../styles/App.css';
export class App extends Component {
componentDidMount() {
const { dispatch } = this.props;
dispatch(searchPhotos(window.store));
}
searchPhotosSubmit = () => {
const { dispatch } = this.props;
dispatch(setFirstPage());
dispatch(setMainImageIndex(0));
dispatch(searchPhotos(window.store));
}
render() {
const { fetchError } = this.props;
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Flickr Slider in React.js</h1>
<SearchForm onSubmit={this.searchPhotosSubmit} />
</header>
{!fetchError ? <Slider /> : <Error />}
</div>
);
}
}
export default connect(state => ({
fetchError: state.fetchError,
form: state.form,
slider: state.slider,
}))(App);
Please not that you export both presentational component (as named export) and container component (as default export) in App.js. Then in your tests you import and use the presentational component using:
import { App } from '../components/App';
but you should import connected container component instead using:
import App from '../components/App'; // IMPORTANT! - no braces around `App`
Since you're using component that is not connected to Redux store dispatch prop is not injected as prop. Just use correct import and it should work.
For more details about importing default and named exports please check this doc. About presentational and container components you can read here.

Resources