Redux form test using jest - reactjs

I am trying to test redux form submit for some code similar to this file.
https://github.com/marmelab/admin-on-rest/blob/master/src/mui/auth/Login.js
My code is like this
const middlewares = [];
const mockStore = configureMockStore(middlewares);
it("submit button", () => {
userLogin = jest.fn();
const initialState = {
admin: {
notification: {
text: "",
type: "info"
}
},
};
store = mockStore(initialState);
tn = label => label;
const props = {
submitting: false,
theme: customTheme,
translate: tn,
store,
location: {
state: {
nextPathname: "/"
}
},
userLogin: userLogin
};
container = mount(
<Provider store={store}>
<TranslationProvider locale="en">
<Login {...props} /> //Login is connected component
</TranslationProvider>
</Provider>
,
{
context: { store: mockStore(initialState) }
}
);
const username = container.find("TextField").first();
username.simulate("change", { target: { value: "admin" } });
const password = container.find("TextField").last();
password.simulate("change", { target: { value: "Superuser" } });
const form = container.find("form");
form.simulate("submit");
console.log(username.debug());
expect(userLogin).toHaveBeenCalled();
});
I face two problems.
When I print the username then I dont see the update value that I update through simulate.
Secondly, the expect clause fails. How do I ensure that userLogin function in my code got called.
Expected mock function to have been called.

This is how I test my redux-forms using JEST snapshot testing.
import React from 'react'
import renderer from 'react-test-renderer'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import YourReduxFormComponent from 'path where it sitting in your project'
import { reduxForm } from 'redux-form'
jest.mock('react-dom')
const spy = jest.fn()
const initialStateValues = {/* initial state values that your form component expects */}
const Decorated = reduxForm({
form: 'testForm', onSubmit: { spy }
})(YourReduxFormComponent)
const formFieldValues = {/*Form field values*/}
it('YourReduxFormComponent renders correctly', () => {
const store = createStore((state) => state, initialStateValues)
const tree = renderer.create(
<Provider store={store}>
<Decorated
{...formFieldValues}
/>
</Provider>
).toJSON()
expect(tree).toMatchSnapshot()
})
//Make your own tests like above to test the values

Related

React component test case coverage of inline functions

Can someone help me cover this test case, I am not able to figure out how to cover this inline function
Note: DropdownField is a wrapper component and contains the actual which is imported from
import { Field } from "redux-form";
dropdown input inside
I have tried to call mockfunction and jest.fn() but nothing works, Any help will be appreciated because I am totally blank at the moment. Thanks in advance to all the wonderful devs
import React from "react";
import DropdownField from "components/FormFields/DropdownField";
import get from "lodash/get";
 
const AddressLookup = props => {
const {
change,
formValues,
fetchAddressLookup,
postalCodeOptions,
type = "delivery",
placeholder = "type_to_search",
field
} = props;
const selectedDeliveryMethod = get(formValues, "delivery_method", {});
 
return (
<DropdownField
placeholder={placeholder}
options={postalCodeOptions}
{...selectedDeliveryMethod.fields.zip_code}
isSearchable={true}
field={field}
onInputChange={value => {
if (value.length >= 2) fetchAddressLookup({ q: value });
}}
onChange={({ value }) => {
const [city, state, zipCode] = value.split("-");
change(field, value);
change(`${type}_state`, state);
change(`${type}_city`, city);
change(`${type}_zip_code`, zipCode);
}}
/>
);
};
 
export default AddressLookup;
I have tried this approach but It failed to cover. First test case covers the UI part only as you can see it is matching to snapshot. In second test cases I removed some code and commented some because nothing works
import * as React from 'react';
import { render, fireEvent, wait } from '#testing-library/react';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { reduxForm } from 'redux-form';
import configureStore from 'redux-mock-store';
import messages from '__fixtures__/messages.json';
import AddressLookup from '../index';
const DecoratedAddressLookup = reduxForm({ form: 'testForm' })(AddressLookup);
const testProps = {
change: jest.fn(),
fetchAddressLookup: jest.fn(),
postalCodeOptions: [
{
name: 'abc-abcd-1234',
value: 'abc-abcd-1234',
},
],
formValues: {
delivery_method: {
fields: {
zip_code: 'BD-BDBD-1234',
},
},
},
field: 'zip_code',
};
describe('<AddressLookup />', () => {
let testStore;
let addressField;
const mockStore = configureStore([]);
const store = mockStore({});
const spy = jest.fn();
beforeAll(() => {
testStore = store;
});
const renderComponent = () => {
return render(
<Provider store={testStore}>
<IntlProvider locale='en' messages={messages}>
<DecoratedAddressLookup
{...testProps}
onInputChange={spy}
onChange={spy}
/>
</IntlProvider>
</Provider>
);
};
it('should render and match the snapshot', () => {
const {
getByTestId,
container: { firstChild },
} = renderComponent();
addressField = getByTestId('zip_code');
expect(firstChild).toMatchSnapshot();
});
it('should type a value', async () => {
addressField = addressField.querySelector('input');
// expect(addressField).toBeTruthy();
// console.log('addressField', addressField);
// const input = screen.getByTestId('add-word-input');
fireEvent.change(addressField, { target: { value: 'abc-abcd-1234' } });
expect(addressField).toHaveValue('abc-abcd-1234');
// expect(testProps.change).toBeCalled();
await wait(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});

testing-library/react rendering empty <div /> for a redux toolkit component

The issue it produces is it won't successfully render the MyComponent to the Mock DOM. console.log store displays the correct state in the store as well. But it's just rendering the empty in the body tag.
import React from 'react';
import configureMockStore from 'redux-mock-store';
import * as actions from 'store/reducer/reducer';
import { fireEvent, render, screen } from 'testUtils';
import MyComponent from 'components/MyComponent';
import { initialState } from 'store/reducer/reducer';
const mockStore = configureMockStore();
describe('my component', () => {
let message;
beforeAll(() => {
message = 'testing';
});
it('test 1', () => {
const store = mockStore({
myState: {
...initialState,
message,
},
});
render(<MyComponent />, {
store: store,
});
screen.debug();
expect(screen.queryAllByText(message).length).toBe(1);
});
});
// in testUtils
function render(ui, { store = configureStore(), ...renderOptions } = {}) {
function Wrapper({ children }) {
return (
// ..some other Providers
<Provider store={store}>
{children}
</Provider>
);
}
export {render};
now the screen.debug() only shows
<body>
<div />
</body>
// in MyComponent
const MyComponent = (): JSX.Element => {
const dispatch = useDispatch();
const myState = useSelector(myReducer);
return (
<AnotherComponent
isOpen={myState?.isOpen}
message={myState?.message}
/>
);
};
I found the reason why it's not working. It's because, in myComponent, I had a typo in the conditional statement. Thanks, everyone.

TypeError: ref.collection is not a function using React-Redux-Firebase/React-Testing-Library

I have been having a ton of trouble trying to render a react-redux-firebase connected component using react-testing-library. I keep getting hit with a [TypeError: ref.collection is not a function] error that crashes my test as soon as I try to run it.
The component:
#firestoreConnect((props, {getState}) => {
const {auth} = getState();
const {clientId} = auth.data;
return [
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'users'}]
},
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'scripts'}]
},
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'teams'}]
},
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'lists'}],
where: [
['status', '==', 'complete'],
['archived', '==', false]
]
}
];
})
#connect(({auth, profile, ggFirestore}, {params}) => {
const {clientRef, displayName, legacyClientId, permissions} = auth.data;
return {
users: ggFirestore.getIn(['users']),
lists: ggFirestore.getIn(['lists']),
scripts: ggFirestore.getIn(['scripts']),
teams: ggFirestore.getIn(['teams']),
profile,
clientRef,
displayName,
legacyClientId,
permissions,
params
};
})
export default class CampaignForm extends Component {
constructor(props) {
super(props);
const campaignTypes = {
'1to1sms': [
VisibilityStep,
TemplatesStep,
ListsStep,
OwnerStep,
NameStep,
SummaryStep
],
'1to1email': [
NameStep,
TemplatesStep,
ListsStep,
SendEmailsStep,
SuccessStep
]
};
this.state = {
step: 0,
contactTypeSteps: campaignTypes[props.params.contactType],
campaignId: '',
draftCampaignId: '',
type: 'organizing',
isProcessing: false,
confirmSummary: false,
contactType: props.params.contactType
};
}
...
render() {
const {
step,
success,
error,
contactTypeSteps,
campaignId,
draftCampaignId,
isProcessing,
confirmSummary,
contactType
} = this.state;
const {params, lists, users, teams, scripts, profile} = this.props;
const isLastStep =
contactTypeSteps.length === step + 1 ||
(contactType === '1to1email' && step === 3);
const StepComponent = contactTypeSteps[step];
if (profile && profile.role === 'volunteer') {
return <PermissionDenied />;
}
if (users && lists && scripts && teams) {
return (
<div className="top-spacer" date-testid="_campaign-form">
<SuccessMessage
message={success}
handleDismiss={() => {
this.setState({success: null});
}}
/>
<ErrorMessage
error={error}
handleDismiss={() => {
this.setState({error: null});
}}
/>
{confirmSummary ? (
<SuccessStep
campaignId={campaignId ? campaignId : draftCampaignId}
campaignType={params.contactType}
{...this.state}
{...this.props}
isOrganizer={profile && profile.role === 'organizer'}
/>
) : (
<StepComponent
handleNext={
isLastStep ? ::this.handleFinalSubmit : ::this.handleNextClick
}
handlePrev={::this.handleBackClick}
handleChange={::this.handleChange}
contactType={params.contactType}
isLastStep={isLastStep}
isProcessing={isProcessing}
{...this.props}
{...this.state}
isOrganizer={profile && profile.role === 'organizer'}
/>
)}
</div>
);
}
return <Loading />;
}
}
My test wrapper:
import '#testing-library/jest-dom/extend-expect';
import 'firebase/firestore';
import { render } from '#testing-library/react';
import firebase from 'firebase';
import { createMemoryHistory } from 'history';
import React from 'react';
import { Provider } from 'react-redux';
import { reactReduxFirebase } from 'react-redux-firebase';
import { MemoryRouter } from 'react-router-dom';
import { compose, createStore } from 'redux';
import { reduxFirestore } from 'redux-firestore';
import rootReducer from '../ducks';
jest.mock('../store/fbTestConfig', () => ({
firebase: {
firestore: jest.fn(() => ({})),
initializeApp: jest.fn(() => {})
}
}));
const mockStore = compose(
reactReduxFirebase(firebase, {}),
reduxFirestore(firebase)
)(createStore);
export function renderWithRedux(
Component,
{
initialState,
store = mockStore(rootReducer, initialState),
route = '/',
history = createMemoryHistory({initialEntries: [route]})
} = {}
) {
return {
...render(
<Provider store={store}>
<MemoryRouter initialEntries={[route]}>{Component}</MemoryRouter>
</Provider>,
{}
),
store,
history
};
}
My test:
import '#testing-library/jest-dom/extend-expect';
import { cleanup } from '#testing-library/react';
import React from 'react';
import CampaignForm from '../../components/Campaigns/CampaignForm';
import { renderWithRedux } from '../renderWithRedux';
beforeEach(cleanup);
test('CampaignForm renders', () => {
const {debug} = renderWithRedux(<CampaignForm />, {
route: 'organize/1to1sms'
});
debug();
});
I tried console logging the component and it looks like it is wrapped properly by firestoreConnect, but the ref key is always set to null. I have tried looking everywhere for some kind of answer but no luck. Any help would be GREATLY appreciated!
So I finally figured it out! The component was expecting props that I did not seed properly when setting up my test.
In my component I had these lines:
const {auth} = getState();
const {clientId} = auth.data;
Where auth is a piece of my application state. The initial state for this was an empty Map, so when I tried to pull clientId from there and use it to get my collections, it threw an error.
The solution was just using my mock store to dispatch the appropriate action to populate the auth Map. Worked like a charm.

mock useDispatch in jest and test the params with using that dispatch action in functional component

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')
})
});
[upd] I've changed my mind dramatically since then. Now I think 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.
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*/);
});
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!
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"));
});
I see advantages in using actual <Provider store={store}>:
much easier to write tests
much more readable since just store's data is actually mocked(one mock instead of multiple - and sometimes inconsistent - mocks for 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):
mocking all the server requests might be a huge task
typically we already have that logic covered with test on per-slice basis
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);
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.

translate is not a function Admin-On-Rest test

I am trying to test empty username text field on my login page based on admin-on-rest Login page.
https://github.com/marmelab/admin-on-rest/blob/master/src/mui/auth/Login.js
My testcase is like this.
import Login, { renderTextField } from '../../../modules/core/Login';
import {propTypes, reduxForm, Field, reducer as formReducer} from 'redux-form';
import redux, { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
describe ('<Login/>', () => {
let container
let submitting
let onSave
let store
let tn
beforeEach(()=>{
submitting = false
//tn= sinon.spy()
onSave = Promise.resolve()
})
it("shows help text when first name is set to blank", () => {
store = createStore(combineReducers({ form: formReducer }))
tn = (label) => label;
const props = {
onSave,
submitting: false,
theme: customTheme,
translate: tn,
store
}
container = mount(
<Provider store={store}>
<Login {...props}/>
</Provider>
)
const input = container.find('TextField').first()
input.simulate('blur')
console.log(input.debug())
expect(input.props.errorText).to.equal('Required')
})
})
I get an error in the redux form validate function.
TypeError: translate is not a function
at validate (C:/src/modules/core/Login.js:165:25)
ie this line on the link in above code
errors.username = translate('aor.validation.required');
How can I test this?
You have to include the TranslationProvider too
import { TranslationProvider } from 'admin-on-rest';
/* ... */
it("shows help text when first name is set to blank", () => {
store = createStore(combineReducers({ form: formReducer }))
const props = {
onSave,
submitting: false,
theme: customTheme,
store
}
container = mount(
<Provider store={store}>
<TranslationProvider locale="en">
<Login {...props}/>
</TranslationProvider>
</Provider>
)
const input = container.find('TextField').first()
input.simulate('blur')
console.log(input.debug())
expect(input.props.errorText).to.equal('Required')
})

Resources