I am using formik and useDispatch for form and submit functionality. I was able to test that the action is dispatched on submit, only when I comment out the API call code. If I don't comment out, throws an error.
How to call a mock API call instead of the actual API? or how to use redux-mock-store for formik+asynchronous API call?
ForgotPassword.js:
<>
<Typography variant='h6'>Recover Password</Typography>
<Formik
initialValues={{ username: '' }}
onSubmit={(values, { setSubmitting }) => {
dispatch(forgotPassword(values.username)).then(() => {
setSubmitting(false)
})
}}
validationSchema={validations}
>
<Form noValidate>
<Field
name='forgotPassword'
render={formikProps => (
<>
<Field
variant='outlined'
margin='normal'
required
fullWidth
id='username'
label='Username'
name='username'
autoComplete='username'
component={TextField}
autoFocus
/>
<Button type='submit' />
</>
)}
/>
</Form>
</Formik>
</>
ForgotPassword.test.js
import React from 'react'
import ForgotPassword from '../../components/public/ForgotPassword'
import { mount } from 'enzyme'
import { Provider } from 'react-redux'
import configureStore from 'redux-mock-store'
import * as ReactReduxHooks from '../react-redux-hooks'
import thunk from 'redux-thunk'
describe('#ForgotPassword', () => {
let wrapper
const middleWare = [thunk]
const mockStore = configureStore(middleWare)
let store
beforeEach(() => {
const initialState = {
auth: { tempUsername: '' },
}
store = mockStore(initialState)
jest
.spyOn(ReactReduxHooks, 'useSelector')
.mockImplementation(state => store.getState())
jest
.spyOn(ReactReduxHooks, 'useDispatch')
.mockImplementation(() => store.dispatch)
wrapper = mount(
<Provider store={store}>
<ForgotPassword />
</Provider>
)
})
it('expect value changes after simulate change', async () => {
wrapper.find('input').simulate('change', {
persist: () => {},
target: { value: 'jhonny123', name: 'username' },
})
expect(wrapper.find('input').props().value).toBe('jhonny123')
wrapper.find('button').simulate('submit')
await new Promise(resolve => {
setTimeout(() => resolve(), 0)
})
const actions = store.getActions()
console.log(actions)
//when the API call part, is commented out inside the actions, prints the actions nicely
//and if I don't comment out the API call part, throws an error
})
})
actions which dispatches
export const forgotPassword = username => {
return async dispatch => {
dispatch(setTempUsername(username))
// await Auth.forgotPassword(username)
// .then(() => {
// dispatch(setResettingPassword(true))
// })
// .catch(err => {
// /*dispatch(showError(err.message)*/
// })
dispatch(
showSuccess(
'A verification code has been sent to the email linked to your username.'
)
)
}
}
Here is the console.log when the API call part is commented out inside the actions
[
{ type: 'auth/setTempUsername', payload: 'jhonny123' },
{
type: 'snackbar/handleSnackbar',
payload: {
verticalPosition: 'bottom',
horizontalPosition: 'center',
message: 'A verification code has been sent to the email linked to your username.',
autoHideDuration: 10000,
messageType: 'success',
isOpen: true
}
}
]
Error when api call is not commented out
TypeError: Cannot read property 'clientMetadata' of undefined
407 | return async dispatch => {
408 | dispatch(setTempUsername(username))
> 409 | await Auth.forgotPassword(username)
| ^
410 | .then(() => {
411 | dispatch(setResettingPassword(true))
412 | })
Since 2.1.0, Redux Thunk supports injecting a custom argument, you can use it for the apis you used in your app, for example:
const store = configureStore({
reducer: {
auth: authReducer,
},
middleware: [
getDefaultMiddleware({
thunk: {
extraArgument: storeAuth,
},
}),
],
})
then in your thunk acion remove import Auth and extract it from third argument:
return async (dispatch, getState, { storeAuth }) => {
dispatch(setTempUsername(username))
await storeAuth.forgotPassword(username)
.then(() => {
dispatch(setResettingPassword(true))
})
to test it you should create the mock store and add mock api as extra argument to thunk
const storeAuth = {
forgotPassword: jest.fn(() => Promise.resolve({ data: {} })),
}
const middleWare = [thunk.withExtraArgument(storeAuth)]
const mockStore = configureStore(middleWare)