Search code examples
reactjsreact-reduxjestjsreact-final-form

How To Mock redux-promise-listener Middleware And final-form


I setup react-redux-promise-listener (repo) for react-final-form via author instructions. Now, I'm having trouble mocking it for testing.

I'm getting error: The redux-promise-listener middleware is not installed

I've tried applying middleware within the test provider:

// app/frontend/__mocks__/TestReduxProvider.js
import React from 'react';
import { createStore, applyMiddleware } from 'redux';
import { Provider as ReduxProvider } from 'react-redux';
import { compose, setDisplayName, defaultProps } from 'recompose';
import createReduxPromiseListener from 'redux-promise-listener';

import rootReducer from '../store/rootReducer';

const displayName = 'TestReduxProvider';

const enhance = compose(
  setDisplayName(displayName),
  defaultProps({ state: {} }),
);

const reduxPromiseListener = createReduxPromiseListener();

function TestReduxProvider(props) {
  const { state, children } = props;
  const store = createStore(
    rootReducer,
    state,
    applyMiddleware(reduxPromiseListener.middleware),
  );

  return <ReduxProvider store={store}>{children}</ReduxProvider>;
}

export const promiseListener = reduxPromiseListener;
export default enhance(TestReduxProvider);

// sample test 

import React from 'react';
import TestReduxProvider from '~/__mocks__/TestReduxProvider';

import { render } from 'enzyme';
import Form from '../Form';

it('should not break if no params', () => {
  const wrapper = render(
    <TestReduxProvider>
      <Form />
    </TestReduxProvider>
  );
  expect(wrapper).toMatchSnapshot();
});
// form component relevant

import { connect } from 'react-redux';
import { Form as FormHandler } from 'react-final-form';

import MakeAsyncFunction from 'react-redux-promise-listener';
import { promiseListener } from '~/store/createStore';
import {
  STATUS_SUCCESS,
  STATUS_FAILURE,
} from '~/store/modules/async/constants';


const Form = props => {
  const targetFormErrors = formErrors[targetAction] || '';

  return (
    <StyledForm >
      <MakeAsyncFunction
        listener={promiseListener}
        start={targetAction}
        resolve={`${targetAction}/${STATUS_SUCCESS}`}
        reject={`${targetAction}/${STATUS_FAILURE}`}
        setPayload={setPayloadHandler}
      >
        {asyncFunc => (
          <FormHandler
            onSubmit={asyncFunc}
            validate={isValid}
            initialValues={formattedInitialValues}
            render={({ handleSubmit, pristine, errors, form }) => {
              if (navigationState !== 'form') {
                form.reset();
              }

              return (
                <form
                  onSubmit={event => {
                    setSubmitAttempted(true);
                    handleSubmit(event)
                      .then(() => submitSuccessHandler(form))
                      .catch(() => {
                      });
                  }}
                >
                  {inputsArray.map(formObject => {
                    return (
                      <div key={formObject.property}>
                        <label>
                          {formObject.label}
                          {formObject.isRequired !== false && !isDisabled && (
                            <span className="required"> *</span>
                          )}
                          <InputContainer>
                            <Input
                              formObject={formObject}
                              isDisabled={isDisabled}
                            />
                          </InputContainer>
                        </label>
                      </div>
                    );
                  })}
                </form>
              );
            }}
          />
        )}
      </MakeAsyncFunction>
    </StyledForm>
  );
};

export default enhance(Form);



I was expecting the snapshot to render. However, I got an error instead:


The redux-promise-listener middleware is not installed

      103 |     },
      104 |   ];
    > 105 |   const wrapper = render(
          |                   ^
      106 |     <TestReduxProvider>
      107 |       <Form inputsArray={INPUTS_ARRAY} />
      108 |     </TestReduxProvider>

      at Object.createAsyncFunction (node_modules/redux-promise-listener/dist/redux-promise-listener.cjs.js:60:13)
      at new MakeAsyncFunction (node_modules/react-redux-promise-listener/dist/react-redux-promise-listener.cjs.js:82:31)
      at processChild (node_modules/react-dom/cjs/react-dom-server.node.development.js:2846:14)
      at resolve (node_modules/react-dom/cjs/react-dom-server.node.development.js:2812:5)
      at ReactDOMServerRenderer.render (node_modules/react-dom/cjs/react-dom-server.node.development.js:3202:22)
      at ReactDOMServerRenderer.read (node_modules/react-dom/cjs/react-dom-server.node.development.js:3161:29)
      at Object.renderToStaticMarkup (node_modules/react-dom/cjs/react-dom-server.node.development.js:3661:27)
      at Object.renderToStaticMarkup [as render] (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:742:31)
      at render (node_modules/enzyme/src/render.js:21:25)
      at Object.<anonymous> (app/frontend/components/views/dashboard/views/accounts/components/__tests__/Form.test.js:105:19)

Solution

  • The issue I had with reproducing the live environment in the test environment was the promiseListener. I ran into this issue by importing directly from store file in the component:

    import { promiseListener } from '~/store/createStore';
    

    solution:

    // index.js
    import createStore, { promiseListener } from './store/createStore';
    import { Main } from './components';
    import PromiseListenerContext from '~/store/PromiseListenerContext';
    
    const app = (
      <ReduxProvider store={appStore}>
        <PromiseListenerContext.Provider value={promiseListener}>
          <Main />
        </PromiseListenerContext.Provider>
      </ReduxProvider>
    
    // createStore.js
    
    import { compose, applyMiddleware, createStore } from 'redux';
    import createReduxPromiseListener from 'redux-promise-listener';
    
    const reduxPromiseListener = createReduxPromiseListener();
    
    function createStoreWrapper(history, testMiddleware) {
      const middleware = [
        reduxPromiseListener.middleware,
      ];
      if (testMiddleware) middleware.push(testMiddleware);
    
      const enhancers = [responsiveStoreEnhancer, applyMiddleware(...middleware)];
    
      const initialState = getInitialState();
      const enhancer = compose(...enhancers);
    
      const store = createStore(rootReducer, initialState, enhancer);
    
      addHandlers(store);
    
      return store;
    }
    
    export const promiseListener = reduxPromiseListener;
    
    export default createStoreWrapper;
    
    // test store 
    import React from 'react';
    import { createStore, applyMiddleware } from 'redux';
    import { Provider as ReduxProvider } from 'react-redux';
    import { compose, setDisplayName, defaultProps } from 'recompose';
    import createReduxPromiseListener from 'redux-promise-listener';
    
    import rootReducer from '../store/rootReducer';
    import PromiseListenerContext from '~/store/PromiseListenerContext';
    
    const displayName = 'TestReduxProvider';
    
    const enhance = compose(
      setDisplayName(displayName),
      defaultProps({ state: {} })
    );
    
    function TestReduxProvider(props) {
      const { state, children } = props;
    
      const reduxPromiseListener = createReduxPromiseListener();
    
      const store = createStore(
        rootReducer,
        state,
        applyMiddleware(reduxPromiseListener.middleware)
      );
    
      return (
        <ReduxProvider store={store}>
          <PromiseListenerContext.Provider value={reduxPromiseListener}>
            {children}
          </PromiseListenerContext.Provider>
        </ReduxProvider>
      );
    }
    
    // test file 
    import React from 'react';
    import { render } from 'enzyme';
    import Main from '../Main';
    import TestReduxProvider from '~/__mocks__/TestReduxProvider';
    
    it('should not break with basic params', () => {
      const wrapper = render(
        <TestReduxProvider>
          <Main pageObject={{}} />
        </TestReduxProvider>
      );
      expect(wrapper).toMatchSnapshot();
    });