Reputation: 395
Hi I am writing test for functional component using the jest and enzyme. and When I simulate a click then params(state of component using useState) of component change. and when state is changed then useEffect call and in useEffect I am dispatching some asynchronous actions with params after changed. So I want to test params with I am dispatching the action. for this I want to mock dispatch. How can I achieve this ? Anyone can help me, thanks in advance. Below I am sharing the code.
component.js
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector, useDispatch } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { clientOperations, clientSelectors } from '../../store/clients';
import Breadcrumb from '../../components/UI/Breadcrumb/Breadcrumb.component';
import DataTable from '../../components/UI/DataTable/DataTable.component';
import Toolbar from './Toolbar/Toolbar.component';
const initialState = {
search: '',
type: '',
pageNo: 0,
rowsPerPage: 10,
order: 'desc',
orderBy: '',
paginated: true,
};
const Clients = ({ history }) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const totalElements = useSelector(state => state.clients.list.totalElements);
const records = useSelector(clientSelectors.getCompaniesData);
const [params, setParams] = useState(initialState);
useEffect(() => {
dispatch(clientOperations.fetchList(params));
}, [dispatch, params]);
function updateParams(newParams) {
setParams(state => ({
...state,
...newParams,
}));
}
function searchHandler(value) {
updateParams({
search: value,
pageNo: 0,
});
}
function typeHandler(event) {
updateParams({
type: event.target.value,
pageNo: 0,
});
}
function reloadData() {
setParams(initialState);
}
const columns = {
id: t('CLIENTS_HEADING_ID'),
name: t('CLIENTS_HEADING_NAME'),
abbrev: t('CLIENTS_HEADING_ABBREV'),
};
return (
<>
<Breadcrumb items={[{ title: 'BREADCRUMB_CLIENTS' }]}>
<Toolbar
search={params.search}
setSearch={searchHandler}
type={params.type}
setType={typeHandler}
reloadData={reloadData}
/>
</Breadcrumb>
<DataTable
rows={records}
columns={columns}
showActionBtns={true}
deletable={false}
editHandler={id => history.push(`/clients/${id}`)}
totalElements={totalElements}
params={params}
setParams={setParams}
/>
</>
);
};
Component.test.js
const initialState = {
clients: {
list: {
records: companies,
totalElements: 5,
},
},
fields: {
companyTypes: ['All Companies', 'Active Companies', 'Disabled Companies'],
},
};
const middlewares = [thunk];
const mockStoreConfigure = configureMockStore(middlewares);
const store = mockStoreConfigure({ ...initialState });
const originalDispatch = store.dispatch;
store.dispatch = jest.fn(originalDispatch)
// configuring the enzyme we can also configure using Enjym.configure
configure({ adapter: new Adapter() });
describe('Clients ', () => {
let wrapper;
const columns = {
id: i18n.t('CLIENTS_HEADING_ID'),
name: i18n.t('CLIENTS_HEADING_NAME'),
abbrev: i18n.t('CLIENTS_HEADING_ABBREV'),
};
beforeEach(() => {
const historyMock = { push: jest.fn() };
wrapper = mount(
<Provider store={store}>
<Router>
<Clients history={historyMock} />
</Router>
</Provider>
);
});
it('on changing the setSearch of toolbar should call the searchHandler', () => {
const toolbarNode = wrapper.find('Toolbar');
expect(toolbarNode.prop('search')).toEqual('')
act(() => {
toolbarNode.props().setSearch('Hello test');
});
toolbarNode.simulate('change');
****here I want to test dispatch function in useEffect calls with correct params"**
wrapper.update();
const toolbarNodeUpdated = wrapper.find('Toolbar');
expect(toolbarNodeUpdated.prop('search')).toEqual('Hello test')
})
});
Upvotes: 26
Views: 90090
Reputation: 23735
Warning! This goes against best practices and recommendations from Redux team! Use this as a last resort!
Mocking store(with redux-mock-store
or even real store that changes its state) - and wrapping component with <Provider store={mockedStore}>
- is way more reliable and convenient. Check another answer below.
-- original answer below --
if you mock react-redux
you will be able to verify arguments for useDispatch
call. Also in such a case you will need to re-create useSelector
's logic(that's really straightforward and actually you don't have to make mock be a hook). Also with that approach you don't need mocked store or <Provider>
at all.
import { useSelector, useDispatch } from 'react-redux';
const mockDispatch = jest.fn();
jest.mock('react-redux', () => ({
useSelector: jest.fn(),
useDispatch: () => mockDispatch
}));
it('loads data on init', () => {
const mockedDispatch = jest.fn();
useSelector.mockImplementation((selectorFn) => selectorFn(yourMockedStoreData));
useDispatch.mockReturnValue(mockedDispatch);
mount(<Router><Clients history={historyMock} /></Router>);
expect(mockDispatch).toHaveBeenCalledWith(/*arguments your expect*/);
});
Upvotes: 49
Reputation: 23735
I see advantages in using actual <Provider store={store}>
:
useDispatch
and useSelector
)But introducing real store with real reducer(s) and real dispatching looks like overkill to me as for unit testing(but would be ok to integration testing):
With this in mind have picked configureStore
from redux-mock-store
instead redux
and got next helper(uses Enzyme):
import { act } from 'react-dom/test-utils';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
function renderInRedux(
children,
initialData = {}
) {
let state = initialData;
const store = (configureMockStore([thunk]))(() => state);
const wrapper = mount(
<Provider store={store}>
{children}
</Provider>
);
return {
/*
since Enzyme wrappers are readonly, we need retrieve target element in unit test each time after any interaction
*/
getComponent() {
return wrapper.childAt(0);
},
/*
set store to any desired config; previous value is replaced;
*/
replaceStore(newState) {
act(() => {
state = newState;
store.dispatch({ type: dummyActionTypeName }); // just to trigger listeners
});
wrapper.update();
},
/*
bridge to redux-mock-store's getActions
*/
getActions() {
return store.getActions().filter(({ type }) => type !== dummyActionTypeName);
},
/*
bridge to redux-mock-store's clearActions()
*/
clearActions() {
return store.clearActions();
},
};
}
And example of usage:
const {
getComponent,
replaceStore,
} = renderInRedux(<Loader />, { isRequesting: false });
expect(getComponent().isEmptyRender()).toBeTruthy();
replaceStore({ isRequesting: true });
expect(getComponent().isEmptyRender()).toBeFalsy();
But how would it help to avoid mocking server side interaction if we want to test dispatching? Well, by itself it does not. But we can mock and test action dispatching in easy way:
import { saveThing as saveThingAction } from '../myActions.js';
jest.mock('../myActions.js', () => ({
saveThing: jest.fn().mockReturnValue({ type: 'saveThing' })
}));
beforeEach(() => {
});
....
const { getComponent, getActions } = renderInRedux(
<SomeForm />,
someMockedReduxStore
);
getComponent().find(Button).simulate('click');
expect(getActions()).toContainEqual(saveThingAction());
expect(saveThingAction).toHaveBeenCalledWith(someExpectedArguments);
Upvotes: 3
Reputation: 550
import * as redux from "react-redux";
describe('dispatch mock', function(){
it('should mock dispatch', function(){
//arrange
const useDispatchSpy = jest.spyOn(redux, 'useDispatch');
const mockDispatchFn = jest.fn()
useDispatchSpy.mockReturnValue(mockDispatchFn);
//action
triggerYourFlow();
//assert
expect(mockDispatchFn).toHaveBeenCalledWith(expectedAction);
//teardown
useDispatchSpy.mockClear();
})
}});
From functional component we mock dispatch like above to stop it to execute the real implementation. Hope it helps!
Upvotes: 29
Reputation: 31
This is how I solved using react testing library:
I have this wrapper to render the components with Provider
export function configureTestStore(initialState = {}) {
const store = createStore(
rootReducer,
initialState,
);
const origDispatch = store.dispatch;
store.dispatch = jest.fn(origDispatch)
return store;
}
/**
* Create provider wrapper
*/
export const renderWithProviders = (
ui,
initialState = {},
initialStore,
renderFn = render,
) => {
const store = initialStore || configureTestStore(initialState);
const testingNode = {
...renderFn(
<Provider store={store}>
<Router history={history}>
{ui}
</Router>
</Provider>
),
store,
};
testingNode.rerenderWithProviders = (el, newState) => {
return renderWithProviders(el, newState, store, testingNode.rerender);
}
return testingNode;
}
Using this I can call store.dispatch
from inside the test and check if it was called with the action I want.
const mockState = {
foo: {},
bar: {}
}
const setup = (props = {}) => {
return { ...renderWithProviders(<MyComponent {...props} />, mockState) }
};
it('should check if action was called after clicking button', () => {
const { getByLabelText, store } = setup();
const acceptBtn = getByLabelText('Accept all');
expect(store.dispatch).toHaveBeenCalledWith(doActionStuff("DONE"));
});
Upvotes: 3
Reputation: 9826
import * as ReactRedux from 'react-redux'
describe('test', () => {
it('should work', () => {
const mockXXXFn = jest.fn()
const spyOnUseDispatch = jest
.spyOn(ReactRedux, 'useDispatch')
.mockReturnValue({ xxxFn: mockXXXFn })
// Do something ...
expect(mockXXXFn).toHaveBeenCalledWith(...)
spyOnUseDispatch.mockRestore()
})
})
UPDATE: DO NOT use React Redux hooks API which is strongly coupling with Redux store implementation logic, make it very difficult to test.
Upvotes: 1