reactjsreduxjestjsformikredux-mock-store

while testing onSubmit I want to Mock API call instead of making actual API call


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

Solution

  • 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)