How to test HOC with enzyme, chai - reactjs

I have a HOC function that receives a React component and returns that react component with two new method properties (handleBack & moveitOnTop) like so:
import React, { Component } from "react";
import classNames from "classnames";
export default WrappedComponent => {
return class extends Component {
constructor(props) {
super(props);
this.moveitOnTop = this.moveitOnTop.bind(this);
this.handleBack = this.handleBack.bind(this);
this.state = {
isSearchActive: false
};
}
moveitOnTop(flag) {
this.setState({ isSearchActive: flag });
window.scrollTo(0, -100);
}
handleBack() {
this.setState({ isSearchActive: false });
if (document.body.classList.contains("lock-position")) {
document.body.classList.remove("lock-position");
}
}
render() {
const props = {
...this.props,
isSearchActive: this.state.isSearchActive,
moveitOnTop: this.moveitOnTop,
goBack: this.handleBack
};
const classes = classNames({ "c-ftsOnTop": this.state.isSearchActive });
return (
<div className={classes}>
<WrappedComponent {...props} />
</div>
);
}
};
};
The component:
//import fireAnalytics
import { fireAnalytics } from "#modules/js-utils/lib";
class MyComponent extender Component{
constructor(){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
// calling analytics function by passing vals
fireAnalytics({
event: "GAEvent",
category: "",
action: `Clicked on the ${e.target.id} input`,
label: "Click"
});
// I CALL THE HOC PROPERTY
this.props.moveitOnTop(true);
// I CALL THE HOC PROPERTY
this.props.handleBack();
}
render(){
return(
<div className="emailBlock">
<input type="text" onClick={handleClick} />
<Button className="submit">Submit</Button>
</div>
)
}
}
// export HOC component
export default hoc(MyComponent);
// export just MyComponent
export {MyComponent};
I want to test the HOC:
I need to check that class .c-ftsOnTop exists
I need to check onClick function that calls this.props.handleBack & `this.props.moveitOnTop'
I need to check if className emailBlock exists.
The test that I tried, but fails:
import { mount, shallow } from 'enzyme';
import sinon from 'sinon';
import React from 'react';
import { expect } from 'chai';
import hoc from '....';
import {MyComponent} from '...';
import MyComponent from '....';
it('renders component', () => {
const props = {}
const HocComponent = hoc(MyComponent);
const wrapper = mount(
<HocComponent {...props} />
);
console.log('wrapper:', wrapper);
expect(wrapper.find('.c-ftsOnTop')).to.have.lengthOf(1);
expect(wrapper.hasClass('c-fts-input-container')).toEqual(true);
})
Error
AssertionError: expected {} to have a length of 1 but got 0
console.log: wrapper: ReactWrapper {}
Can anybody help me on how to render the HOC?

Here is a working test:
import { mount } from 'enzyme';
import React from 'react';
import WrappedMyComponent from './MyComponent';
it('renders component', () => {
const props = {}
const moveitOnTopSpy = jest.spyOn(WrappedMyComponent.prototype, 'moveitOnTop');
const handleBackSpy = jest.spyOn(WrappedMyComponent.prototype, 'handleBack');
const wrapper = mount(
<WrappedMyComponent {...props} />
);
// 1. I need to check that class .c-ftsOnTop exists
wrapper.setState({ isSearchActive: true }); // <= set isSearchActive to true so .c-ftsOnTop is added
expect(wrapper.find('.c-ftsOnTop')).toHaveLength(1); // Success!
// 2. I need to check onClick function that calls this.props.handleBack & `this.props.moveitOnTop'
window.scrollTo = jest.fn(); // mock window.scrollTo
wrapper.find('input').props().onClick();
expect(moveitOnTopSpy).toHaveBeenCalled(); // Success!
expect(window.scrollTo).toHaveBeenCalledWith(0, -100); // Success!
expect(handleBackSpy).toHaveBeenCalled(); // Success!
// 3. I need to check if className emailBlock exists
expect(wrapper.find('.emailBlock')).toHaveLength(1); // Success!
})
Details
.c-ftsOnTop is only added when isSearchActive is true so just set the state of the component so the class is added.
If you create your spies on the prototype methods for moveitOnTop and handleBack, then when the hoc creates its instance methods by binding them to this in the constructor, the instance methods will be bound to your spies.
window.scrollTo logs an error to the console by default in jsdom so you can mock it to avoid that error message and to verify that it was called with the expected arguments.
Note that the above test requires the following typos to be fixed in MyComponent:
extender should be extends
constructor should take a props argument
onClick should be bound to this.handleClick instead of just handleClick
handleClick should call this.props.goBack() instead of this.props.handleBack()
(I'm guessing MyComponent was just thrown together as an example of the actual component)

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.

enzyme shallow rendering just one node in redux components

Enzyme shallow rendering behaves in an unexpected way if I am rendering a redux component with a mocked store.
I have a simple test that looks like this :
import React from 'react';
import { shallow } from 'enzyme';
import { createMockStore } from 'redux-test-utils';
import Test from './Test'
it('should render ', () => {
const testState = {
app: {
bar: ['a', 'b', 'c']
}
};
const store = createMockStore(testState)
const context = {
store,
};
const shallowComponent = shallow(<Test items={[]}/>, {context});
console.log(shallowComponent.debug());
}
The Test component looks like :
class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
export default Test;
Which as expected prints out this :
<div className="here" />
However if my component is a redux component :
class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
const mapStateToProps = state => {
return {
barData: state.app.bar
}
}
export default connect(
mapStateToProps
)(Test)
Then what I get in the console is this :
<BarSeriesListTest items={{...}} barData={{...}} dispatch={[Function]} />
Why is there this difference? How do I test that my component has <div className="here"/> embedded in it in my redux version of the component?
You are referencing the HOC that connect is returning and not the component that you want to test.
You should use enzyme's dive function which will render the child component and return it as a wrapper.
const shallowComponent = shallow(<Test items={[]}/>, {context}).dive();
You can use it multiple times if you have multiple components that you need to dive through to get to. It's better than using mount as well because we are still testing in isolation.
You should export the unconnected component and test it separately (notice the first export):
export class Test extends React.Component {
}
...
export default connect(
mapStateToProps
)(Test)
While in your test you should test the rendering of the unconnected component like so (notice the curly braces around { Test }):
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Test } from './Test';
describe('...', () => {
it('...', () => {
const wrapper = shallow(<Test />)
expect(toJson(wrapper)).toMatchSnapshot();
})
})
Hope this helps.
Mode specifically for your described case the component should be:
import React from 'react';
import { connect } from 'react-redux';
export class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
const mapStateToProps = state => {
return {
barData: state.app.bar
}
}
export default connect(
mapStateToProps
)(Test)
The test spec should be:
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Test } from 'Test';
describe('Test component', () => {
it('renders', () => {
const wrapper = shallow(<Test />);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
Which generates the following snapshot:
exports[`Test component renders 1`] = `
<div
className="here"
/>
`;
You are exporting the connected component by default. What you can do is import the component that is not connected to redux.
import { Test } from './Test';
Then your test should work.

React-Dimension getWrappedInstance() returns undefined in enzyme test

I ran into an issue on create test for a reactJs component that is wrapped by React-Dimension as an HOC. Here is the simple set up
import React, { PropTypes } from 'react';
import Dimensions from 'react-dimensions';
export class CustomComponent extends React.Component {
static propTypes = {
messageProp: PropTypes.string.isRequired
};
constructor(props, context) {
super(props, context);
this.state = {
message: props.messageProp
};
};
// a simple function to be tested
sayHi () {
return this.state.message;
}
render () {
return (<div>{this.sayHi()}</div>);
}
}
export default Dimensions({elementResize: true})(CustomComponent);
And here is the test
import React from 'react';
import { mount, shallow } from 'enzyme';
import CustomComponent from 'components/CustomComponent/CustomComponent.js';
describe('<CustomComponent /> presentation', () => {
let _props = {
messageProp: 'Hi Message !!'
}
it('HOC Wrapped Custom test ', () => {
const c = mount(<CustomComponent {..._props}/>);
const inst = c.instance();
const childInst = inst.getWrappedInstance();
expect(childInst.sayHi()).to.equal('Hi Message !!');
});
});
The getWrappedInstance() is returning undefined. If I don't put the Dimension as the wrapping HOC, the test case will work just fine. Did I missed something so the return of the getWrappedInstance() is undefined?
I have tried to use mount or sallow and not changing the result. What I need to test is the sayHi function of the CustomComponent in this case.

"This method is only meant to be run on single node. 0 found instead" Enzyme error

I have a simple component that toggles sorting. When a link is clicked, it fires a function. When I run shallow() on it, I'm getting an error like
Error: This method is only meant to be run on single node. 0 found instead
My component is:
import React, { Component, PropTypes } from 'react';
import { sortGames } from '../actions';
import { connect } from 'react-redux';
class SortList extends Component {
constructor(props) {
super(props);
this.onSortGames = this.props.onSortGames.bind(this);
}
componentWillMount(){
this.setState({
sortByIncrease: false
})
}
render() {
return (
<div className="sort">
<span>Sort by:
<a href="#" onClick={e => {
e.preventDefault();
this.props.onSortGames(this.props.filter, this.state.sortByIncrease);
this.setState({
sortByIncrease: !this.state.sortByIncrease
});
}}>
{ (this.state.sortByIncrease) ? "Decrease" : "Increase" }
</a>
</span>
</div>
)
}
}
SortList.propTypes = {
onSortGames: PropTypes.func.isRequired
};
const mapStateToProps = (state) => ({
games: state.games
});
const mapDispatchToProps = (dispatch) => ({
onSortGames(filter, asc) {
dispatch(sortGames(filter, asc));
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(SortList);
and here is my test script:
import React from 'react';
import {expect} from 'chai';
import { shallow, mount } from 'enzyme';
import sinon from 'sinon';
import SortList from '../components/SortList';
import configureStore from '../configureStore';
describe('SortList', () => {
const store = configureStore();
const props = {
filter: "all",
sortByIncrease: false,
onSortGames : (a,b) => {}
};
it('should render sort list component', () => {
const wrapper = shallow(<SortList {...props} store={store}></SortList>);
expect(wrapper.length).to.equal(1);
});
it('should call sorting function when clicked', () => {
const onSortGames = sinon.spy();
const wrapper = shallow(<SortList {...props} store={store}></SortList>);
console.log(wrapper.debug());
wrapper.find('a').simulate('click');
expect(onSortGames.calledOnce).to.equal(true);
});
});
The console.log(wrapper.debug()); statement prints
<SortList filter="all" sortByIncrease={false} onSortGames={[Function]} store={{...}} games={{...}} />
What am I doing wrong? It has to be reaching the a tag, I believe, but still...
I find out shallow can't find child elements on connected components, so they export component and connected component separately.
Here's how they do on redux examples treeview example node component
so i changed my component like
class SortList extends Component {
to
export class SortList extends Component {
and
export default connect(
mapStateToProps,
mapDispatchToProps
)(SortList);
to
const SortListConnected = export default connect(
mapStateToProps,
mapDispatchToProps
)(SortList);
export default SortListConnected;
changed my test scripts import to
import {SortList} from '../components/SortList';
after that find() worked well.
You are creating a sinon spy but not introducing it into your shallow function. You need to pass onSortGames spy into props before you pass it to the component. As of right now when you click it is simply calling the function you listed in the properties in your describe clause onSortGames : (a,b) => {}.
Try directly applying the spy to the JSX, something like:
it('should call sorting function when clicked', () => {
const onSortGames = sinon.spy();
const wrapper = shallow(<SortList onSortGames={onSortGames} {...props} store={store}></SortList>);
console.log(wrapper.debug());
wrapper.find('a').simulate('click');
expect(onSortGames.calledOnce).to.equal(true);
});

How to mock a wrapper component with Jest?

Assume the component is working, I have just removed useless code. In my component to test, I have this:
import {Container} from "flux/utils";
import Dialog from "material-ui/Dialog";
import React from "react";
...
class Component extends React.Component {
state;
static calculateState() {
return {
...
};
}
static getStores() {
return [...];
}
...
render() {
var actions = [...];
return <Dialog
actions={actions}
open={this.state.open}
title="foo"
...
>
<form ...>
{/* My inputs and their values are according to the state. */}
</form>
</Dialog>;
}
}
const MyComponent = Container.create(Component);
export default MyComponent;
In the UI, the inputs and their values are working. However, when I use Jest, it is not.
jest.mock('react-dom');
jest.mock('material-ui/Dialog', () => 'Dialog');
import Dispatcher from "../../dispatcher/Dispatcher";
import MyComponent from "../MyComponent";
import React from "react";
import Renderer from "react-test-renderer";
describe('MyComponent', () => {
it('creates', () => {
const component = Renderer.create(<MyComponent/>);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
Dispatcher.dispatch({
action: 'myComponent/open'
});
tree = component.toJSON();
expect(tree).toMatchSnapshot();
tree.children[0].children[1].children[0].props.onChange(undefined, undefined, 'john');
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
The first snapshot is good. The second snapshot is good too, the open property is changing. However, when I trigger the onChange of an input, the component state is updating, all good... but when I do the last snapshot, the children are still the same, event if the input values are different (and the state is well updated, confirmed).
I know this is due to the Dialog. If I remove it from the component class, the component works as expected... So how should I declare the mock?
Here is my last try:
jest.mock('material-ui/Dialog', () => {
var React = require("react");
return class extends React.Component {
shouldComponentUpdate() {
return true;
}
render() {
var {children, ...rest} = this.props;
return children;
}
}
});
The children are still the same :(

Resources