Jest / React / Redux - MapDispatchToProps Undefined - reactjs

I am trying to learn React w/ Jest / Enzyme.
I have a component that receives 2 props -
loadTenantListAction,
filterTenantListAction,
These props are passed in via mapDispatchToProps -
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import {
loadTenantListAction,
filterTenantListAction,
} from '../../store/actions';
import TenantList from './TenantList';
const mapStateToProps = tenants => ({ tenants });
const mapDispatchToProps = {
loadTenantListAction,
filterTenantListAction,
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(TenantList)
);
I have declared propTypes in my component as such -
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class TenantList extends Component {
static propTypes = {
loadTenantListAction: PropTypes.func.isRequired,
filterTenantListAction: PropTypes.func.isRequired,
};
render() {
return <p>Foo</p>;
}
}
My unit test is failing now showing that these props are marked as required, but are undefined. I expect this, as I am not passing them into my test -
import React from 'react';
import { shallow } from 'enzyme';
import TenantListContainer from '../../../src/containers/TenantList';
import TenantList from '../../../src/containers/TenantList/TenantList';
describe('<TenantList />', () => {
it('should render the TenantList Component', () => {
const wrapper = shallow(<TenantListContainer />);
expect(wrapper.find(<TenantList />)).toBeTruthy();
});
});
I can pass the test doing something like
expect(
wrapper.find(
<TenantList
loadTenantListAction={() => {}}
filterTenantListAction={() => {}}
/>
)
).toBeTruthy();
But that does not seem right at all, nor do I expect to be able to write useful tests by carrying on like that.
How should I be handling props passed in via mapDispatchToProps?

You can pass props directly to your component in shallow method.
describe('<TenantList />', () => {
const props = {
loadTenantListAction: () => {}, // or you can use any spy if you want to check if it's called or not
filterTenantListAction () => {},
}
it('should render the TenantList Component', () => {
const wrapper = shallow(<TenantListContainer {...props} />);
expect(wrapper.find(<TenantList />)).toBeTruthy();
});
});

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.

React - Test separate parent component (without redux)

I wanna test parent component, but I want to do this without redux. I have problem because I've got error:
Invariant Violation: Could not find "store" in either the context or props of "Connect(MarkerList)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(MarkerList)".
My parent component:
export class Panel extends React.Component {
constructor(props) {
}
componentDidUpdate(prevProps) {
...
}
handleCheckBox = event => {
...
};
switchPanelStatus = bool => {
...
};
render() {
...
}
}
const mapDispatchToProps = {
isPanelSelect
};
export const PanelComponent = connect(
null,
mapDispatchToProps
)(Panel);
My child component:
export class MarkerList extends Component {
constructor(props) {
...
};
}
componentDidMount() {
...
}
componentDidUpdate() {
...
}
onSelect = (marker, id) => {
...
}
render() {
...
}
}
const mapStateToProps = state => ({
...
});
const mapDispatchToProps = {
...
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MarkerList);
Panel test file:
import '#testing-library/jest-dom'
import "#testing-library/react"
import React from 'react'
import {render, fireEvent, screen} from '#testing-library/react'
import {Panel} from '../Panel';
test("test1", async () => {
const isPanelSelect = jest.fn();
const location = {
pathname: "/createMarker"
}
const {getByText} = render( <Panel isPanelSelect={isPanelSelect} location={location} />)
})
I've tried set store as props to Panel component or wrap It via Provider in my test file but It doesn't help me.
react-redux doesn't work without the store. You can either provide it by the context or props (usually in tests). You can provide a mock version in the test. The main problem is that both components require Redux. You have to manually forward the context to the children if it's provided as prop. The alternative solution is to mount your component within a Redux aware tree:
import { Provider } from "react-redux";
test("test1", async () => {
const { getByText } = render(
<Provider store={createFakeStore()}>
<Panel isPanelSelect={isPanelSelect} location={location} />
</Provider>
);
});

How to test function that passed from mapDispatchToProps (React/Redux/Enzyme/Jest)

I want to test that function passed from mapDispatchToProps was invoked when button clicking is simulated.
How to test that function which passed from mapDispatchToProps is invoked?
I tried to pass a mocked function by props, but it doesn't work. Any help will be appreciated.
Here below my fake class code and test example.
My component
// All required imports
class App extends React.Component<Props> {
render() {
const { onClick } = this.props;
return (
<>
<h1>Form</h1>
<input />
<button onClick={() => onClick()} />
</>
);
}
}
const mapStateToProps = (state) => {
return {
state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onClick: () => dispatch(actions.onClick())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
My test file
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16/build/index';
import jsdom from 'jsdom';
import React from 'react';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import ConnectedApp, { App } from './App';
function setUpDomEnvironment() {
const { JSDOM } = jsdom;
const dom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'http://localhost/' });
const { window } = dom;
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
}
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
Object.defineProperties(target, props);
}
setUpDomEnvironment();
configure({ adapter: new Adapter() });
const mockStore = configureMockStore();
describe('App', () => {
describe('When App connected to store', () => {
describe('When button clicked', () => {
it('should not crush after click on login button', () => {
const onClick = jest.fn()
const store = mockStore(initialStates[1]);
const wrapper = mount(
<Provider store={store}>
<ConnectedApp />
</Provider>);
wrapper.find('button').simulate('click');
??? how to test that function passed from mapDispatchToProps was fired?
});
});
});
});
I recommend following the approach described in the docs and export the connected component as the default export for use in the application, and export the component itself as a named export for testing.
For the code above export the App class and test the click like this:
import * as React from 'react';
import { shallow } from 'enzyme';
import { App } from './code';
describe('App', () => {
it('should call props.onClick() when button is clicked', () => {
const onClick = jest.fn();
const wrapper = shallow(<App onClick={onClick} />);
wrapper.find('button').simulate('click');
expect(onClick).toHaveBeenCalledTimes(1);
});
});
shallow provides everything that is needed for testing the component itself. (shallow even calls React lifecycle methods as of Enzyme v3)
As you have found, to do a full rendering of the component requires a mock redux store and wrapping the component in a Provider. Besides adding a lot of complexity, this approach also ends up testing the mock store and all child components during the component unit tests.
I have found it much more effective to directly test the component, and to export and directly test mapStateToProps() and mapDispatchToProps() which is very easy since they should be pure functions.
The mapDispatchToProps() in the code above can be tested like this:
describe('mapDispatchToProps', () => {
it('should dispatch actions.onClick() when onClick() is called', () => {
const dispatch = jest.fn();
const props = mapDispatchToProps(dispatch);
props.onClick();
expect(dispatch).toHaveBeenCalledWith(actions.onClick());
});
});
This approach makes unit testing the component very simple since you can pass the component props directly, and makes it very simple to test that the component will be handed the correct props by the pure functions (or objects) passed to connect().
This ensures that the unit tests are simple and targeted. Testing that connect() and redux are working properly with the component and all of its child components in a full DOM rendering can be done in the e2e tests.

Enzyme shallow dive fails with connected component

I try to test my connect component. Running this test:
import React from 'react';
import { shallow } from 'enzyme';
import Dashboard from './Dashboard';
describe('Dashboard', () => {
test('should render correctly according to the snapshot', () => {
const wrapper = shallow(<Dashboard />).dive();
expect(wrapper).toMatchSnapshot();
});
});
According to this comment this should work but fails:
node_modules/subscriptions-transport-ws/node_modules/iterall/index.mjs:84
export var $$iterator = SYMBOL_ITERATOR || '##iterator'
^^^^^^
SyntaxError: Unexpected token export
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/ScriptTransformer.js:289:17)
at Object.<anonymous> (node_modules/subscriptions-transport-ws/dist/utils/empty-iterable.js:3:17)
at Object.<anonymous> (node_modules/subscriptions-transport-ws/dist/server.js:8:24)
The dashboard is connected with react-redux's connnect HOC:
const mapStateToProps = (state) => ({
apolloLink: state.apollo.apolloLink,
});
export default connect(mapStateToProps)(Dashboard);
Am I missing something?
When testing a component that uses redux, you need to mock your store and provide it to your component before testing it. You can use the "redux-mock-store" npm package to do so.
For Example:
import React from 'react';
import { shallow } from 'enzyme';
import Dashboard from './Dashboard';
import configureStore from 'redux-mock-store'
describe('Dashboard', () => {
initialState = { apollo: { apolloLink: 'test value goes here' } }
let store, wrapper;
beforeEach(() => {
store = mockStore(initialState)
wrapper = shallow(<Dashboard store={store} />).dive()
})
it("Should render correctly according to snapshot", () => {
let tree = wrapper.toJSON();
expect(tree).toMatchSnapshot();
});
});

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

Resources