Describe the bug
I'm trying to unit test a react component (called SayConfig) which has nested components and which is wrapped inside a router and redux form. However, due to the router and redux form, I have to call .dive() several times in order to access to the component I want to test. But when trying to .dive() on
Invariant Violation: You should not use
or withRouter() outside a Router
What I want to do is to make unit tests on mapStateToProps() and mapDispatchToProps().
Here is the code:
_SayConfigComponent.jsx (contains JSX definition of the component)_
export default ({
saveTemplate,
handleSubmit,
templates,
makeTemplateEditable,
onChangeTemplateText,
finishTemplateEditionAction,
deleteTemplate,
configUI,
editTemplate,
closePanel,
}:
FullProps) => (
<Panel className="SayConfigPanel">
<Panel.Heading>
<Glyphicon glyph="remove" onClick={() => closePanel()} />
<Panel.Title componentClass="h3">My Title</Panel.Title>
</Panel.Heading>
<Panel.Body>
{configUI.showEditionForm ?
<form onSubmit={
handleSubmit(data => editTemplate(data)}
>
<ControlLabel>Edit an existing template</ControlLabel>
<Field
name="newTemplate"
component={EditTemplateField}
onChange={onChangeTemplateText}
text={configUI.templateToEdit.text}
/>
<Button bsStyle="success" className="pull-right" type="submit">Edit</Button>
<Button bsStyle="danger" className="pull-right" onClick={finishTemplateEditionAction}>Cancel</Button>
</form>
:
<form onSubmit={handleSubmit(data => saveTemplate(data))} >
<ControlLabel>Add a template</ControlLabel>
<Field
name="template"
component={AddTemplateField}
/>
<Button bsStyle="primary" className="pull-right" type="submit">Save</Button>
</form>
}
<Templates
templates={templates}
makeTemplateEditable={makeTemplateEditable}
deleteTemplate={deleteTemplate}
/>
</Panel.Body>
</Panel>
);
_index.js (contains mapStateToProps and mapDispatchToProps and connects the component to the router and redux form)_
import SayConfigComponent from './component';
const mapStateToProps = (state: State, { bot, domain, nodeId }): StateProps => {
// some lines use bot, domain and nodeId
const templates: Array<string> = getTemplatesFromSomewhere();
return {
templates,
configUI: state.ui.config,
};
};
const mapDispatchToProps = (dispatch: Function, { bot, domain }): DispatchProps => ({
saveTemplate: (data) => {
// dispatch some actions
},
makeTemplateEditable: (index, text) => {
// dispatch some actions
},
editTemplate: (data) => {
// dispatch some actions
},
deleteTemplate: (index) => {
// dispatch some actions
},
finishTemplateEditionAction: () => {
// dispatch some actions
},
onChangeTemplateText: (event) => {
// dispatch some actions
},
closeSayConfig: () => {
// dispatch some actions
},
});
const connectedWrapper = withRouter(connect(
mapStateToProps,
mapDispatchToProps,
)(SayConfigComponent));
export default reduxForm({
form: 'sayConfigTemplateForm',
fields: ['template', 'newTemplate'],
})(connectedWrapper);
_index.spec.js (supposed to contain the test for mapStateToProps and mapDispatchToProps)_
import React from 'react';
import { shallow } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import SayConfigIndex from './index';
const mockStore = configureMockStore();
describe('SayConfigComponent', () => {
let wrapper;
let store;
beforeEach(() => {
const initialState = {
// some params for initial state
};
store = mockStore(initialState);
wrapper = shallow(<SayConfigIndex bot="test" domain="test" nodeId="n2" />, { context: { store } }).dive().dive().dive().dive();
// returns Invariant Violation: You should not use <Route> or withRouter() outside a <Router>
// If I remove one dive(), i get the following component : <Route render={[Function: render]} />
});
it('MapStateToProps > it should properly return nodeId, say templates of selected node and sayConfigUI', () => {
console.log('debug: ', wrapper.debug());
console.log('selector:', wrapper.find('.SayConfigPanel'));
});
});
My question is : how can I test such a component and what are the best practices?
Hi @willemotlucas there is a good explanation in redux docs, and you can find it by this link.
In short if you want to test a component which is wrapped with any wrapper you can export an original component and test it instead of a wrapped component.
The same can be applied for mapStateToProps and mapDispatchToProps in your case. You can export them as well and cover by unit tests.
// your component file
class YourComponent extends React.Component {
// component logic here
}
export const mapStateToProps = () => { /* your logic here */ };
export { YourComponent };
export default connect(mapStateToProps)(YourComponent);
// your test file
import { mapStateToProps, YourComponent } from './your/component'
describe('your component description', () => {
describe('YourComponent', () => {
test('test description', () => {
// your test here
});
});
describe('mapStateToProps', () => {
test('should return a mapped state to props object', () => {
// your test here
});
});
});
In case if you want to test real user scenarios unit tests are not a solution for it, because the whole idea of them is to test some unit of logic not a whole user case. In order to cover real user scenarios e2e tests would work better. You can find more info by this link or you can google it :)
Next image illustrates a case when unit tests (doors can be closed/opened) are passing but application does not work as expected:

This seems answered.
Thanks @mykhailo-riabokon, that was a lifesaver
Thanks @mykhailo-riabokon !!!
connect
The above example doesn't explain a component wrapped inside withRouter and then connect.
Enzyme definitely lags behind this area.
@maxpaynestory that's two HOCs, so you'd need two .dive()s. If there's something more, please file a new issue.
Most helpful comment
Hi @willemotlucas there is a good explanation in
reduxdocs, and you can find it by this link.In short if you want to test a component which is wrapped with any wrapper you can export an original component and test it instead of a wrapped component.
The same can be applied for
mapStateToPropsandmapDispatchToPropsin your case. You can export them as well and cover by unit tests.In case if you want to test real user scenarios unit tests are not a solution for it, because the whole idea of them is to test some unit of logic not a whole user case. In order to cover real user scenarios e2e tests would work better. You can find more info by this link or you can google it :)
Next image illustrates a case when unit tests (doors can be closed/opened) are passing but application does not work as expected: