Redux pass / mock dispatch actions while testing higher order components - reactjs

While I am trying to test an Redux connected react component, I am unable to provide .Below is the code for my connected react component :
MyComponent.jsx
class MyComponent extends React.Component{
componentWillMount (){
}
componentDidMount () {
setTimeOut (()=>{
changeValue('personName' , 'something',someVar)
},2000);
}
}
export default MyComponent
index.jsx
import MyComponent from './MyComponent'
const mapStateToProps = state => (
{
rootState : state
info : state.someInfo
}
)
const mapDispatchProps = dispatch => bindActionCreators({
changeValue : change,
resetValue : reset
},dispatch)
export default compose (connect (mapStateToProps,mapDispatchProps),injector,theme.wizard({}))(
connect(state => ({
values : selector (state, 'vehicleName', 'address','personName')
}))
)(MyComponent)
MyComponent.Test.js
import configureStore from 'redux-mock-store'
import {shallow, mount} from 'enzyme'
import MyComponent from './MyComponent'
jest.useFakeTimers();
describe('test1', ()=>{
it('test for componentDidMount' , ()=>{
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {changeValue : (x,y,z)=> console.log(x)};
const store = mockStore(initialState);
var component = shallow (<Provider store = {store}><MyComponent /></Provider>);
component.update();
jest.runAllTimers();
})
}
When I run this I get error : changeValue is not a function , How can I pass/Mock changeValue function in the test to resolve this error.

Related

React component not setting state after mocking redux

Here is my test
const initialRootState = {
accounts: [mockAccounts],
isLoading: false
}
describe('Account Dashboard', () => {
let rootState = {
...initialRootState
}
const mockStore = configureStore()
const store = mockStore({ ...rootState })
const mockFunction = jest.fn()
jest.spyOn(Redux, 'useDispatch').mockImplementation(() => mockFunction)
jest
.spyOn(Redux, 'useSelector')
.mockImplementation((state) => state(store.getState()))
afterEach(() => {
mockFunction.mockClear()
// Reseting state
rootState = {
...initialRootState
}
})
it('renders correctly', () => {
const wrapper = mount(
<TestWrapper>
<AccountDashboard />
</TestWrapper>
)
console.log(wrapper)
})
})
In my component I am mapping accounts from the state. In my test I am getting the following error TypeError: Cannot read property 'map' of undefined
I would like to test an if statement I am using in my component to ensure it's returning the proper view based on the number of accounts I am receiving.
However, when I console.log(store.getState()) it is printing correctly. What am I doing wrong?
If you're going to test a Redux connected component, I'd recommend steering away from mocking its internals and instead to test it as if it were a React component connected to a real Redux store.
For example, here's a factory function for mounting connected components with enzyme:
utils/withRedux.jsx
import * as React from "react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { mount } from "enzyme";
import rootReducer from "../path/to/reducers";
/*
You can skip recreating this "store" by importing/exporting
the real "store" from wherever you defined it in your app
*/
export const store = createStore(rootReducer);
/**
* Factory function to create a mounted Redux connected wrapper for a React component
*
* #param {ReactNode} Component - the Component to be mounted via Enzyme
* #function createElement - Creates a wrapper around the passed in component with incoming props so that we can still use "wrapper.setProps" on the root
* #returns {ReactWrapper} - a mounted React component with a Redux store.
*/
export const withRedux = Component =>
mount(
React.createElement(props => (
<Provider store={store}>
{React.cloneElement(Component, props)}
</Provider>
)),
options
);
export default withRedux;
Now, using the above factory function, we can test the connected component by simply using store.dispatch:
tests/ConnectedComponent.jsx
import * as React from "react";
import withRedux, { store } from "../path/to/utils/withRedux";
import ConnectedComponent from "../index";
const fakeAccountData = [{...}, {...}, {...}];
describe("Connected Component", () => {
let wrapper;
beforeEach(() => {
wrapper = withRedux(<ConnectedComponent />);
});
it("initially shows a loading indicator", () => {
expect(wrapper.find(".loading-indicator")).exists().toBeTruthy();
});
it("displays the accounts when data is present", () => {
/*
Ideally, you'll be dispatching an action function for simplicity
For example: store.dispatch(setAccounts(fakeAccountData));
But for ease of readability, I've written it out below.
*/
store.dispatch({ type: "ACCOUNTS/LOADED", accounts: fakeAccountData }));
// update the component to reflect the prop changes
wrapper.update();
expect(wrapper.find(".loading-indicator")).exists().toBeFalsy();
expect(wrapper.find(".accounts").exists()).toBeTruthy();
});
});
This vastly simplifies not having to mock the store/useSelector/useDispatch over and over when you start to test other Redux connected components.
On a side note, you can skip this entirely if you use react-redux's connect function while exporting the unconnected component. Instead of importing the default export, you can import the unconnected component within your test...
Example component:
import * as React from "react";
import { connect } from "react-redux";
export const Example = ({ accounts, isLoading }) => { ... };
const mapStateToProps = state => ({ ... });
const mapDispatchToProps = { ... };
export default connect(mapStateToProps, mapDispatchToProps)(Example);
Example test:
import * as React from "react";
import { mount } from "enzyme";
import { Example } from "../index";
const initProps = {
accounts: [],
isLoading: true
};
const fakeAccountData = [{...}, {...}, {...}];
describe("Unconnected Example Component", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<Example {...initProps } />);
});
it("initially shows a loading indicator", () => {
expect(wrapper.find(".loading-indicator")).exists().toBeTruthy();
});
it("displays the accounts when data is present", () => {
wrapper.setProps({ accounts: fakeAccountData, isLoading: false });
wrapper.update();
expect(wrapper.find(".loading-indicator")).exists().toBeFalsy();
expect(wrapper.find(".accounts").exists()).toBeTruthy();
});
});
I figured out that my test was working incorrectly due to my selector function in my component being implement incorrectly. So the test was actually working properly!
Note: My team is currently using mocked data(waiting for the API team to finish up endpoints).
Originally the useSelector function in my component(that I was testing) looked like:
const { accounts, isLoading } = useSelector(
(state: RootState) => state.accounts,
shallowEqual
)
When I updated this to:
const { accounts, isAccountsLoading } = useSelector(
(state: RootState) => ({
accounts: state.accounts.accounts,
isAccountsLoading: state.accounts.isLoading
}),
shallowEqual
)
My tests worked - here are my final tests:
describe('App', () => {
let rootState = {
...initialState
}
const mockStore = configureStore()
const store = mockStore({ ...rootState })
jest.spyOn(Redux, 'useDispatch').mockImplementation(() => jest.fn())
jest
.spyOn(Redux, 'useSelector')
.mockImplementation((state) => state(store.getState()))
afterEach(() => {
jest.clearAllMocks()
// Resetting State
rootState = {
...initialState
}
})
it('renders correctly', () => {
const wrapper = mount(
<TestWrapper>
<Dashboard />
</TestWrapper>
)
expect(wrapper.find('[data-test="app"]').exists()).toBe(true)
expect(wrapper.find(verticalCard).exists()).toBe(false)
expect(wrapper.find(horizontalCard).exists()).toBe(true)
})
it('renders multiple properly', () => {
rootState.info = mockData.info
const wrapper = mount(
<TestWrapper>
<Dashboard />
</TestWrapper>
)
expect(wrapper.find(verticalCard).exists()).toBe(true)
expect(wrapper.find(horizontalCard).exists()).toBe(false)
})
})

connect functional component to redux store

I'm modernising a react application that I'm working on and I started to use hooks.
Before the component I had was the classic class component, with the constructor, state and everything. I was hooking it to redux the following way:
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { Provider, connect } from 'react-redux';
import Logs from "NS/src/Screen/Logs"
import store from 'NS/src/Reducers/store'
const LogsStore = connect(state => ({ store: state.store }))(Logs);
Now I'm following a tutorial on Hooks, so I change my component accordingly:
export default function Logs() {
const {status, filteredStatus} = this.props.store
const [filter, setFilter] = React.useState('')
clearLogs = () => {
...
}
...
}
the problem is that now this is undefined. How can I access the store from this kind of component?
this is undefined because functional components do not have an instance, the redux props are available at props which is a regular object
const mapState = state => ({ foo : state.foo })
const Component = connect(mapState)(props =>{
console.log(props.foo)
})
Props are passed as an argument to your functional component:
export default function Logs(props) {
const {status, filteredStatus} = props.store
const [filter, setFilter] = React.useState('')
clearLogs = () => {
...
}
...
}
...and if you're into saving lines:
export default function Logs({ store: { status, filteredStatus } }) {
const [filter, setFilter] = React.useState('')
clearLogs = () => {
...
}
...
}

React Redux: Testing mapStateToProps and mapDispatchToProps with Enzyme/Jest

So I would like to test mapStateToProps and mapDispatchToProps with Enzyme/Jest.
I have a component DrawerAvatar like this:
DrawerAvatar.js
const mapStateToProps = state => ({
isAuthenticated: state.authReducer.isAuthenticated
});
export default compose(
connect(mapStateToProps, null)
)(DrawerAvatar);
DrawerAvatar.test.js
import configureMockStore from 'redux-mock-store';
import connectedDrawerAvatar, { DrawerAvatar } from './DrawerAvatar';
const mockStore = configureMockStore();
it('mapStateToProps should return the right value', () => {
const initialState = {
someState: 123
};
const store = mockStore(initialState);
const wrapper = shallow(<connectedDrawerAvatar store={store} />);
expect(wrapper.props().someState).toBe(123);
});
However, this doesn't work because wrapper.props().someState returns undefined... So I have no clue how to test mapStatesToProps along with redux-mock-store using the connected component.
I don't know neither how to test mapDispatchToProps ..!
I've tried the methods providing in this blog but it doesn't work.
Thank you very much !
EDIT:
This works, but I'm not sure if it really tests the mapStateToProps... Can someone confirm that this is the right way to test mapStateToProps ?
DrawerAvatar.test.js
it('mapStateToProps should return the right value', () => {
const initialState = {
isAuthenticated: false
};
const mockStore = configureMockStore();
const store = mockStore(initialState);
const wrapper = shallow(<connectedDrawerAvatar store={store} />);
expect(wrapper.props().store.getState().isAuthenticated).toBe(false);
});
One way I found from : redux discussion on github is
import React from 'react';
import { shallow } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import ConnectedDrawerAvatar from './DrawerAvatar';
describe('DrawerAvatar', ()=> {
const mockStore = configureMockStore();
it.each([true, false], 'receives correct value from store as props', (value)=> {
const initialState = { authReducer: { isAuthenticated: value } }
const store = mockStore(initialState)
const wrapper = shallow(<ConnectedDrawerAvatar store={store} />)
expect(wrapper.props().isAuthenticated).toBe(value)
})
})
You can also try this instead :
In my opinion Testing mapStateToProps(), you need to identify the props for the particular state. Also used provider which makes the components available that are wrapped in Connect() function.
import React from 'react';
import { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import ConnectedDrawerAvatar from './DrawerAvatar';
describe('DrawerAvatar', ()=> {
let component;
let store;
let value;
beforeEach(() => {
const initialState = {
authReducer: { isAuthenticated: value }
};
store = mockStore(initialState);
component = shallow(<Provider store={store}><ConnectedDrawerAvatar/></Provider>);
});
it('should return exact value from the store(props)', () => {
expect(component.props().isAuthenticated).toBe(value);
});
});
Hope this helps!
Easiest way to test that is to that mapStateToProps directly, like this:
export const mapStateToProps = state => ({
isAuthenticated: state.authReducer.isAuthenticated
});
and have a test like this:
it('mapStateToProps should return the right value', () => {
const mockedState = {
authReducer: {
isAuthenticated: false
}
};
const state = mapStateToProps(mockedState);
expect(state).toBeFalsy();
});
But I dont really see why you should do that.
IMO you should not test connected components. Just export the container class and test it directly.
The reason why one should not test component connection is that it is tested well in the library itself so by testing that you don't really add any value.

mapDispatchToProps props null in jest test

I have just started my first react job (yay!) and am attempting to write unit tests using jest. i am having an issue with mapDispatchToProps in my test.
please note, the test passes, but it is throwing the following error message:
Warning: Failed prop type: The prop testDispatch is marked as required in TestJest, but its value is undefined in TestJest
error seems to be test related to the test as it is not thrown when running the page in the web browser. also, mapStateToProps works fine, included to show it works.
using enzyme/shallow in order not to interact with the <Child/> component.
testJest.js
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import Child from './Child'
export class TestJest extends React.Component {
render() {
return (<div>ABC - <Child /></div>);
}
}
TestJest.propTypes = {
testDispatch: PropTypes.func.isRequired,
testState: PropTypes.arrayOf(PropTypes.string)
};
const mapStateToProps = (state) => {
return {
testState: state.utilization.pets // ['dog','cat']
};
};
const mapDispatchToProps = (dispatch) => {
return {
testDispatch: () => dispatch({'a' : 'abc'})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TestJest);
testJest-test.js
import React from 'react';
import { TestJest } from '../Components/testJest';
import TestJestSub2 from '../Components/TestJestSub2';
import { shallow } from 'enzyme';
describe('TESTJEST', () => {
it('matches snapshot', () => {
const wrapper = shallow(<TestJest />);
expect(wrapper).toMatchSnapshot();
});
});
You can pass empty function to TestJest as testDispatch prop:
it('matches snapshot', () => {
const wrapper = shallow(<TestJest testDispatch={() => {}} />);
expect(wrapper).toMatchSnapshot();
});
or pass jest mock function:
it('matches snapshot', () => {
const wrapper = shallow(<TestJest testDispatch={jest.fn()} />);
expect(wrapper).toMatchSnapshot();
});
if you want to check if testDispatch was called (expect(wrapper.instance().props.testDispatch).toBeCalled();)

Test asynchronous nested component

Say I have the following wrapper component:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {update} from '../../actions/actions'
import LoadFromServerButton from '../LoadFromServerButton'
import {connect} from 'react-redux'
export class FooDisplay extends PureComponent {
render () {
return (
<p>
<span className='foo'>
{this.props.foo}
</span>
<LoadFromServerButton updateFunc={this.props.update} />
</p>
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
FooDisplay.propTypes = {
foo: PropTypes.string
}
export const mapDispatchToProps = (dispatch) => {
return {
update: (foo) => dispatch(update(foo))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FooDisplay)
and the following inner component:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {get} from '../../actions/actions'
import ActiveButton from '../ActiveButton'
import {connect} from 'react-redux'
export class LoadFromServerButton extends PureComponent {
doUpdate () {
return this.props.get().then(this.props.updateFunc)
}
render () {
return (
<ActiveButton action={this.doUpdate.bind(this)} actionArguments={[this.props.foo]} text='fetch serverside address' />
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
export const mapDispatchToProps = (dispatch) => {
return {
get: () => dispatch(get())
}
}
LoadAddressFromServerButton.propTypes = {
updateFunc: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(LoadFromServerButton)
ActiveButton is a very thin wrapper around a button with an onclick and arguments destructuring.
Now lets say that I my get action is written as follows:
export const get = () => dispatch => http('/dummy_route')
.spread((response, body) => dispatch(actOnThing(update, body)))
Now if I write a test like so:
/* global window, test, expect, beforeAll, afterAll, describe */
'use strict'
import React from 'react'
import FooDisplay from './index'
import {mount} from 'enzyme'
import {Provider} from 'react-redux'
import configureStore from '../../store/configureStore'
import nock, {uriString} from '../../config/nock'
import _ from 'lodash'
const env = _.cloneDeep(process.env)
describe('the component behaves correctly when integrating with store and reducers/http', () => {
beforeAll(() => {
nock.disableNetConnect()
process.env.API_URL = uriString
})
afterAll(() => {
process.env = _.cloneDeep(env)
nock.enableNetConnect()
nock.cleanAll()
})
test('when deep rendering, the load event populates the input correctly', () => {
const store = configureStore({
address: {
address: 'foo'
}
})
const display = mount(<Provider store={store}><FooDisplay /></Provider>,
{attachTo: document.getElementById('root')})
expect(display.find('p').find('.address').text()).toEqual('foo')
const button = display.find('LoadFromServerButton')
expect(button.text()).toEqual('fetch serverside address')
nock.get('/dummy_address').reply(200, {address: 'new address'})
button.simulate('click')
})
})
This results in:
Unhandled rejection Error: Error: connect ECONNREFUSED 127.0.0.1:8080
After a little bit of thinking, this is due to the fact that the test does not return a promise, as the button click causes the promise to fire under the hood, therefore, afterAll runs immediatly, cleans nock, and a real http connection goes over the wire.
How do I test this case? I don't seem to have an easy way to return the correct promise... How do I test updates to the DOM resulting from these updates?
In order to mock only one method of the imported module, use .requireActual(...)
jest.mock('../your_module', () => ({
...(jest.requireActual('../your_module')),
YourMethodName: () => { return { type: 'MOCKED_ACTION'}; }
}));
As you mentioned the problem is that you dont have the promise to return from test. So to make get return a know promise you can just mock get directly without using nock:
import {get} from '../../actions/actions'
jest.mock('../../actions/actions', () => ({get: jest.fn}))
this will replace the action module with an object {get: jestSpy}
in your test you can then create a promise and let get return this and also return this promise from your test:
it('', ()=>{
const p = new Promise.resolve('success')
get.mockImplementation(() => p)//let get return the resolved promise
//test you suff
return p
})

Resources