rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.97k stars 867 forks source link

Jest error: baseReducer is not a function #1045

Closed mauriguz closed 5 years ago

mauriguz commented 5 years ago

Hey guys, I'm having an issue with redux-persist when trying to run unit tests on certain components. As the title says, baseReducer inside of _nodemodules/redux-persist/lib/persistReducer.js is not a function.

This only happens when running npm test, which in reality is just running jest + enzyme using CRA's config. Running the app in dev or prod works perfectly.

As far I was able to track, the reducer I pass to persistReducer is not "right", but my concern is that I have no idea why that could be happening, after all, some tests work fine, even if they're connected to redux.

During my research I couldn't find any similar issue/solution, the only one similar is #762 but that's not exactly my issue, so I'm kinda lost in the shadows right now. So far this seems related to redux-persist, but maybe I'm wrong? Any help would be appreciated!

Store.js

import createSagaMiddleware from 'redux-saga';
import { persistStore } from 'redux-persist';

import { rootReducer, rootSaga } from './';

const initialState = window.initialReduxState;
const sagaMiddleware = createSagaMiddleware({
    context: {
        splitClient: null,
        splitManager: null,
        splitFactory: null,
    },
});

// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
    enhancers.push(window.devToolsExtension());
}

const store = createStore(
    rootReducer,
    initialState,
    compose(
        applyMiddleware(sagaMiddleware),
        ...enhancers
    )
);
const persistor = persistStore(store);

sagaMiddleware.run(rootSaga);

export default store;

Redux modules index.js

import { all } from 'redux-saga/effects';
import { reducer as formReducer } from 'redux-form';
import { persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

// Here import all the custom reducer and saga files
import { commonReducer as common, commonSaga } from './modules/common';
import { businessOutcomesReducer as businessOutcomes, businessOutcomesSaga } from './modules/businessOutcomes';
import { insightsReducer as insights, insightsSaga } from './modules/insights';
import { featureFlagsReducer as featureFlags, featureFlagsSaga } from './modules/featureFlags';

const businessOutcomePersistConfig = {
    key: 'businessOutcomes',
    storage: storage,
    whitelist: ['activeFilters'],
};

export const rootReducer = combineReducers({
    form: formReducer,
    common,
    businessOutcomes: persistReducer(businessOutcomePersistConfig, businessOutcomes),
    insights: insights,
    featureFlags: featureFlags,
});

export function* rootSaga() {
    yield all([commonSaga(), businessOutcomesSaga(), insightsSaga(), featureFlagsSaga()]);
}

Test file

import React from 'react';
import { shallow } from 'enzyme';
import "../../../setupTests";
import { Provider } from 'react-redux';
import configureStore from "redux-mock-store";
import {CategorySettings} from './CategorySettings';

jest.mock('react-appinsights', () => ({ default: { withTracking: jest.fn() } }));
const customState = {
    openSettingsPanel: true,
    openDemandCategoriesForm: false,
};

describe('Category Settings Render', () => {
    const props = {      
        user: {},       
        companyData: [{ 'name': 'A1 Finance' }, { 'name': 'A1 Technology' }, { 'name': 'ACME Organization' }],
        match: {
            params: { companyId: '2' }
        },
        isSuccess: true,
        getTags: jest.fn(),
        classes: {}
    };
    let wrapper;

    beforeAll(() => {
        wrapper = shallow(<CategorySettings {...props} />);
    });

    it("Should render correctly", () => {        
        expect(wrapper.exists()).toBe(true);
    })
})

Reducer file


// Actions - **export actions for sagas**
export const SAGA_INITIALIZE = 'AgilityHealth/businessOutcomes/SAGA_INITIALIZE';
export const SAGA_GET_SWIMLANES =
    'AgilityHealth/businessOutcomes/SAGA_GET_SWIMLANES';
export const SAGA_POST_BUSINESS_OUTCOME =
    'AgilityHealth/businessOutcomes/SAGA_POST_BUSINESS_OUTCOME';
export const SAGA_UPDATE_BUSINESS_OUTCOME =
    'AgilityHealth/businessOutcomes/SAGA_UPDATE_BUSINESS_OUTCOME';
export const SAGA_REMOVE_BUSINESS_OUTCOME =
    'AgilityHealth/businessOutcomes/SAGA_REMOVE_BUSINESS_OUTCOME';
export const SAGA_GET_METRICS =
    'AgilityHealth/businessOutcomes/SAGA_GET_METRICS';
export const SAGA_CREATE_NEW_METRIC =
    'AgilityHealth/businessOutcomes/SAGA_CREATE_NEW_METRIC';
export const SAGA_GET_METRIC_TYPES =
    'AgilityHealth/businessOutcomes/SAGA_GET_METRIC_TYPES';
export const SAGA_HANDLE_EXTERNAL_LINKS =
    'AgilityHealth/businessOutcomes/SAGA_HANDLE_EXTERNAL_LINKS';
export const SAGA_GET_TAGS = 'AgilityHealth/businessOutcomes/SAGA_GET_TAGS';
export const SAGA_UPDATE_SORT_ORDER =
    'AgilityHealth/businessOutcomes/SAGA_UPDATE_SORT_ORDER';
export const SAGA_CREATE_NEW_BUSINESS_OUTCOME_TAGS =
    'AgilityHealth/businessOutcomes/SAGA_CREATE_NEW_BUSINESS_OUTCOME_TAGS';
export const SAGA_GET_LINKS_FOR_KEY_RESULTS =
    'AgilityHealth/businessOutcomes/SAGA_GET_LINKS_FOR_KEY_RESULTS';
export const SAGA_EXPORT_EXCEL =
    'AgilityHealth/businessOutcomes/SAGA_EXPORT_EXCEL';

const STORE_TOGGLE_LOADING =
    'AgilityHealth/businessOutcomes/STORE_TOGGLE_LOADING';
const STORE_SET_SWIMLANES =
    'AgilityHealth/businessOutcomes/STORE_SET_SWIMLANES';
const STORE_SET_SUCCESS = 'AgilityHealth/businessOutcomes/STORE_SET_SUCCESS';
const STORE_RESET_SUCCESS =
    'AgilityHealth/businessOutcomes/STORE_RESET_SUCCESS';
const STORE_SET_NEW_BUSINESS_OUTCOME =
    'AgilityHealth/businessOutcomes/STORE_SET_NEW_BUSINESS_OUTCOME';
const STORE_SET_UPDATED_BUSINESS_OUTCOME =
    'AgilityHealth/businessOutcomes/STORE_SET_UPDATED_BUSINESS_OUTCOME';
const STORE_REMOVE_BUSINESS_OUTCOME =
    'AgilityHealth/businessOutcomes/STORE_REMOVE_BUSINESS_OUTCOME';
const STORE_SET_METRICS = 'AgilityHealth/businessOutcomes/STORE_SET_METRICS';
const STORE_SET_NEW_METRIC =
    'AgilityHealth/businessOutcomes/STORE_SET_NEW_METRIC';
const STORE_SET_METRIC_TYPES =
    'AgilityHealth/businessOutcomes/STORE_SET_METRIC_TYPES';
const STORE_RESET_FORM_SUCCESS =
    'AgilityHealth/businessOutcomes/STORE_RESET_FORM_SUCCESS';
const STORE_SET_EXTERNAL_LINKS =
    'AgilityHealth/businessOutcomes/STORE_SET_EXTERNAL_LINKS';
const STORE_SET_TAGS = 'AgilityHealth/businessOutcomes/STORE_SET_TAGS';
const STORE_SET_FILTERS = 'AgilityHealth/businessOutcomes/STORE_SET_FILTERS';
const STORE_SET_NEW_ORDER =
    'AgilityHealth/businessOutcomes/STORE_SET_NEW_ORDER';
const STORE_SET_AVAILABLE_TEAM_ALIGNMENT =
    'AgilityHealth/businessOutcomes/STORE_SET_AVAILABLE_TEAM_ALIGNMENT';

// Initial State
const initialState = {
    loading: false,
    swimlanes: [],
    success: {},
    metricList: [],
    metricTypes: [],
    tags: [],
    activeFilters: null,
    teamAlignmentResults: [],
};

// Reducer
export default function reducer(state = initialState, action = {}) {
    switch (action.type) {
        case STORE_TOGGLE_LOADING:
            return { ...state, loading: !state.loading };
        case STORE_SET_SWIMLANES:
            return { ...state, swimlanes: action.payload };
        case STORE_SET_NEW_BUSINESS_OUTCOME:
            return BusinessOutcomesHelpers.setNewBusinessOutcome(
                state,
                action.payload
            );
        case STORE_SET_UPDATED_BUSINESS_OUTCOME:
            return BusinessOutcomesHelpers.setUpdateBusinessOutcome(
                state,
                action.payload
            );
        case STORE_REMOVE_BUSINESS_OUTCOME:
            return BusinessOutcomesHelpers.setRemoveBusinessOutcome(
                state,
                action.payload
            );
        case STORE_SET_SUCCESS:
            return BusinessOutcomesHelpers.successToggler(
                state,
                'set',
                action.payload
            );
        case STORE_RESET_SUCCESS:
            return BusinessOutcomesHelpers.successToggler(
                state,
                'reset',
                action.payload
            );
        case STORE_SET_METRICS:
            return { ...state, metricList: action.payload };
        case STORE_SET_NEW_METRIC:
            return {
                ...state,
                metricList: [...state.metricList, action.payload],
            };
        case STORE_SET_METRIC_TYPES:
            return { ...state, metricTypes: action.payload };
        case STORE_RESET_FORM_SUCCESS:
            return {
                ...state,
                success: {
                    ...state.success,
                    updateBusinessOutcome: false,
                    postBusinessOutcome: false,
                    deleteBusinessOutcome: false,
                },
            };
        case STORE_SET_EXTERNAL_LINKS:
            return BusinessOutcomesHelpers.setExternalLinks(
                state,
                action.payload
            );
        case STORE_SET_TAGS:
            return { ...state, tags: action.payload };
        case STORE_SET_FILTERS:
            return { ...state, activeFilters: action.payload };
        case STORE_SET_NEW_ORDER:
            return { ...state, swimlanes: action.payload.sortedList };
        case STORE_SET_AVAILABLE_TEAM_ALIGNMENT:
            return { ...state, teamAlignmentResults: action.payload }; 
        case STORE_SET_EXTERNAL_LINKS:
            return BusinessOutcomesHelpers.setExternalLinks(
                state,
                action.payload
            );        
        default:
            return state;
    }
}

// Action creators
export const initializeDashboard = payload => ({
    type: SAGA_INITIALIZE,
    ...payload,
});
export const getSwimlanes = payload => ({
    type: SAGA_GET_SWIMLANES,
    ...payload,
});
export const postBusinessOutcome = payload => ({
    type: SAGA_POST_BUSINESS_OUTCOME,
    payload,
});
export const updateBusinessOutcome = payload => ({
    type: SAGA_UPDATE_BUSINESS_OUTCOME,
    payload,
});
export const removeBusinessOutcome = payload => ({
    type: SAGA_REMOVE_BUSINESS_OUTCOME,
    payload,
});
export const getMetrics = () => ({ type: SAGA_GET_METRICS });
export const createNewMetric = payload => ({
    type: SAGA_CREATE_NEW_METRIC,
    payload,
});
export const getMetricTypes = () => ({ type: SAGA_GET_METRIC_TYPES });
export const createNewBusinessOutcomeTags = (payload) => ({ type: SAGA_CREATE_NEW_BUSINESS_OUTCOME_TAGS, payload });

export const handleExternalLinks = payload => ({
    type: SAGA_HANDLE_EXTERNAL_LINKS,
    payload,
});
export const getTags = payload => ({ type: SAGA_GET_TAGS, payload });
export const updateSortOrder = payload => ({
    type: SAGA_UPDATE_SORT_ORDER,
    ...payload,
});
export const getPossibleLinksForKeyResults = (payload) => ({
    type: SAGA_GET_LINKS_FOR_KEY_RESULTS,
    payload
}); 
export const excelFileForExport = (payload) => ({ type: SAGA_EXPORT_EXCEL, payload });

export const toggleLoading = () => ({ type: STORE_TOGGLE_LOADING });
export const setSwimlanes = payload => ({ type: STORE_SET_SWIMLANES, payload });
export const setSuccess = payload => ({ type: STORE_SET_SUCCESS, payload });
export const resetSuccess = payload => ({ type: STORE_RESET_SUCCESS, payload });
export const setNewBusinessOutcome = payload => ({
    type: STORE_SET_NEW_BUSINESS_OUTCOME,
    payload,
});
export const setUpdatedBusinessOutcome = payload => ({
    type: STORE_SET_UPDATED_BUSINESS_OUTCOME,
    payload,
});
export const setRemoveBusinessOutcome = payload => ({
    type: STORE_REMOVE_BUSINESS_OUTCOME,
    payload,
});
export const setMetrics = payload => ({ type: STORE_SET_METRICS, payload });
export const setNewMetric = payload => ({
    type: STORE_SET_NEW_METRIC,
    payload,
});
export const setMetricTypes = payload => ({
    type: STORE_SET_METRIC_TYPES,
    payload,
});
export const resetFormSuccess = () => ({ type: STORE_RESET_FORM_SUCCESS });
export const setExternalLinks = payload => ({
    type: STORE_SET_EXTERNAL_LINKS,
    payload,
});
export const setTags = payload => ({ type: STORE_SET_TAGS, payload });
export const filterBusinessOutcomes = payload => ({
    type: STORE_SET_FILTERS,
    payload,
});
export const setNewOrder = payload => ({ type: STORE_SET_NEW_ORDER, payload });
export const setAvailableTeamAlignment = (payload) => ({
    type: STORE_SET_AVAILABLE_TEAM_ALIGNMENT,
    payload
});

This is the version I'm using "redux-persist": "^5.10.0",

arvinsim commented 5 years ago

How was this issue resolved?

mauriguz commented 5 years ago

@arvinsim I was able to fix it by changing the way I was importing redux actions into the components. When the error was happening I was importing actions like import { userActions } from '../path/to/redux; path/to/redux is pointing to an index file that it was only importing reducer, helpers and saga and exporting all together with diff names. Later I changed that to import * as userActions from '../path/to/reducer; I stopped using the index file and that was enough to fix the issue.