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

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.

Related

Problems testing a Redux + React app with enzyme:

I have this component
import React, { useEffect } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { CircularProgress } from '#material-ui/core';
import { loadPhones } from './redux/actions/actions.js';
import TablePhones from './Table.js';
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadPhones: () => {
dispatch(loadPhones());
},
};
}
export function App(props) {
useEffect(() => {
props.loadPhones();
}, []);
if (props.phones.data) {
return (
<div className="App">
<div className="introductoryNav">Phones</div>
<TablePhones phones={props.phones.data} />
</div>
);
}
return (
<div className="gridLoadingContainer">
<CircularProgress color="secondary" iconStyle="width: 1000, height:1000" />
<p className="loadingText1">Loading...</p>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
For whom ive written
import React from 'react';
import { render } from '#testing-library/react';
import { Provider } from "react-redux";
import App from './App';
import { shallow, mount } from "enzyme";
import configureMockStore from "redux-mock-store";
const mockStore = configureMockStore();
const store = mockStore({});
describe('App comp testing', () => {
it("should render without throwing an error", () => {
const app = mount(
<Provider store={store}>
<App />
</Provider>
).dive()
expect(app.find('.introductoryNav').text()).toContain("Phones");
});
})
But that test keeps failing
ypeError: Cannot read property 'data' of undefined
I also tried importing App as {App} instead and using shallow testing, but no luck. It gives the same erros, so im left without access to the context, and I cant keep doing my tests
How can I solve this?
You could use the non-default export of your component here and shallow render test if you pass your component the props and don't try to mock the store (if I recall correctly).
I was thinking something like this might work, tesing the "pure" non-store connected version of the component. This seems to be a popular answer for this question as this was asked (in a different way) before here:
import React from 'react';
import { App } from './App';
import { shallow } from "enzyme";
// useful function that is reusable for desturcturing the returned
// wrapper and object of props inside of beforeAll etc...
const setupFunc = overrideProps => {
const props = {
phones: {
...phones, // replace with a mock example of a render of props.phones
data: {
...phoneData // replace with a mock example of a render of props.phones.data
},
},
loadPhones: jest.fn()
};
const wrapper = shallow(<App {...props} />);
return {
wrapper,
props
};
};
// this is just the way I personally write my inital describe, I find it the easiest way
// to describe the component being rendered. (alot of the things below will be opinios on test improvements as well).
describe('<App />', () => {
describe('When the component has intially rendered' () => {
beforeAll(() => {
const { props } = setupFunc();
});
it ('should call loadPhones after the component has initially rendered, () => {
expect(props.loadPhones).toHaveBeenCalled();
});
});
describe('When it renders WITH props present', () => {
// we should use describes to section our tests as per how the code is written
// 1. when it renders with the props present in the component
// 2. when it renders without the props
beforeAll(() => {
const { wrapper, props } = setupFunc();
});
// "render without throwing an error" sounds quite broad or more like
// how you would "describe" how it rendered before testing something
// inside of the render. We want to have our "it" represent what we're
// actually testing; that introductoryNave has rendered with text.
it("should render an introductoryNav with text", () => {
// toContain is a bit broad, toBe would be more specific
expect(wrapper.find('.introductoryNav').text()).toBe("Phones");
});
it("should render a TablePhones component with data from props", () => {
// iirc toEqual should work here, you might need toStrictEqual though.
expect(wrapper.find('TablePhones').prop('phones')).toEqual(props.phones);
});
});
describe('When it renders WITHOUT props present', () => {
it("should render with some loading components", () => {
expect(wrapper.find('.gridLoadingContainer').exists()).toBeTruthy();
expect(wrapper.find('CircularProgress').exists()).toBeTruthy();
expect(wrapper.find('.loadingText1').exists()).toBeTruthy();
});
});
});

Context API Unit test failing with TypeError

I have a HOC component WithContext (in a file conveniently named withContext.js) as follows
import React from 'react';
import { DEFAULT_STATE } from './store';
const MyContext = React.createContext(DEFAULT_STATE);
export function WithContext(Component) {
return function WrapperComponent(props) {
return (
<MyContext.Consumer>
{state => <Component {...props} context={state} />}
</MyContext.Consumer>
);
};
};
and a component that uses it as follows
import React from "react";
import { WithContext } from './withContext';
const MyComp = (context) => {
return (
<div className="flex dir-col" id="MyComp">
<p>This is a test</p>
</div>
)
};
export default WithContext(MyComp);
I also have a unit test that aims to test this MyComp component. The unit test follows
import React from "react";
import {shallow} from "enzyme";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { WithContext } from './withContext';
// We need to configure our 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
Enzyme.configure({
adapter : new Adapter()
})
beforeEach(() => {
jest.resetModules()
})
//Takes the context data we want to test, or uses defaults
const getMyContext = (context = {
state : {}
}) => {
// Will then mock the MyContext module being used in our MyComp component
jest.doMock('withContext', () => {
return {
MyContext: {
Consumer: (props) => props.children(context)
}
}
});
// We will need to re-require after calling jest.doMock.
// return the updated MyComp module that now includes the mocked context
return require('./MyComp').default;
};
describe("MyComp component loading check", () => {
test("Renders the MyComp component correctly", () => {
const MyCompContext = getMyContext();
const wrapper = shallow(<MyComp/>);
// make sure that we are able to find the header component
expect(wrapper.find(".flex").hostNodes().length).toBe(1);
});
});
However this test keeps failing with the message
TypeError: (0 , _withContext.WithContext) is not a function
};
export default WithContext(MyComp);
Can you please let me know what is wrong here ?
Thanks
Looks like you are mocking withContext with jest.doMock but the mock you are returning from the factory function does not contain a WithContext function.
Then when you require('./MyComp').default it is using the withContext mock within your MyComp module and failing when it tries to export default WithContext(MyComp); since the withContext mock does not define a WithContext function.

Props aren't passing inside component in test cases written with Jest and Enzyme

This is my test case
import React from 'react';
import { mount } from 'enzyme';
import CustomForm from '../index';
describe('Custom Form mount testing', () => {
let props;
let mountedCustomForm;
beforeEach(() => {
props = {
nav_module_id: 'null',
};
mountedCustomForm = undefined;
});
const customform = () => {
if (!mountedCustomForm) {
mountedCustomForm = mount(
<CustomForm {...props} />
);
}
return mountedCustomForm;
};
it('always renders a div', () => {
const divs = customform().find('div');
console.log(divs);
});
});
Whenever I run the test case using yarn test. It gives the following error TypeError: Cannot read property 'nav_module_id' of undefined.
I have already placed console at multiple places in order to see the value of props. It is getting set. But it couldn't just pass through the components and give the error mentioned above.
Any help would be appreciated been stuck for almost 2-3 days now.
You have to wrap the component that you want to test in beforeEach method such that it becomes available for all the 'it' blocks, and also you have to take the mocked props that you think you are getting into the original component.
import React from 'react'
import {expect} from 'chai'
import {shallow} from 'enzyme'
import sinon from 'sinon'
import {Map} from 'immutable'
import {ClusterToggle} from '../../../src/MapView/components/ClusterToggle'
describe('component tests for ClusterToggle', () => {
let dummydata
let wrapper
let props
beforeEach(() => {
dummydata = {
showClusters: true,
toggleClustering: () => {}
}
wrapper = shallow(<ClusterToggle {...dummydata} />)
props = wrapper.props()
})
describe(`ClusterToggle component tests`, () => {
it(`1. makes sure that component exists`, () => {
expect(wrapper).to.exist
})
it('2. makes sure that cluster toggle comp has input and label', () => {
expect(wrapper.find('input').length).to.eql(1)
expect(wrapper.find('label').length).to.eql(1)
})
it('3. simulating onChange for the input element', () => {
const spy = sinon.spy()
const hct = sinon.spy()
hct(wrapper.props(), 'toggleClustering')
spy(wrapper.instance(), 'handleClusterToggle')
wrapper.find('input').simulate('change')
expect(spy.calledOnce).to.eql(true)
expect(hct.calledOnce).to.eql(true)
})
})
})

Jest / React / Redux - MapDispatchToProps Undefined

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

"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