Reputation: 513
Has anyone been able to clear Redux between tests? What did you do?
I have a React Native Expo app using Redux.
I have a test that works in isolation, but when I run all tests, it fails because there are state changes left in redux from other tests.
How do I 'flush' the redux store between tests.
I tried the mock-redux-store package, but when I passed a combineReducers() to it, it returned nothing.
I could rebuild the state slices in there manually, and pass them to the mock store but that would be hard to maintain.
testing-library-utils.js
:
//This file adds a Redux Provider to a component's context when running Jest tests from test files.
//This first import enables jest native, which enables some state-centric matchers
import '@testing-library/jest-native/extend-expect';
import React from 'react';
import { render } from '@testing-library/react-native';
import { Provider, combineReducers, applyMiddleware, compose } from 'redux';
import ReduxThunk from 'redux-thunk';
import { createMockStore } from 'redux-test-utils';
// import { store } from '../App';
import logBooksReducer from '../store/reducers/logBooks-reducer';
import authReducer from '../store/reducers/auth-reducer';
const composedMiddleWare = compose(applyMiddleware(ReduxThunk));
const rootReducer = combineReducers({
logBooks: logBooksReducer,
auth: authReducer,
});
const store = createMockStore(rootReducer, composedMiddleWare);
console.log('STORE ', JSON.stringify(store.getState()));
const AllTheProviders = ({ children }) => {
console.log('Store.getState() from UTILS ', store.getState());
return <Provider store={store}>{children}</Provider>;
};
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
// re-export everything
export * from '@testing-library/react-native';
// override render method
export { customRender as render };
Upvotes: 4
Views: 4500
Reputation: 513
Finally got it!
This is my modified version of what's in the Redux Testing Docs
//This file adds a Redux Provider to a component's context when running Jest tests from test files.
//This first import enables jest native, which enables some state-centric matchers
import '@testing-library/jest-native/extend-expect';
import React from 'react';
import { render as rtlRender } from '@testing-library/react-native'; // This line is important
import { Provider } from 'react-redux';
import { rootReducer } from '../App'; //Importing my same Root Reducer, so maintaining is easier
import { NavigationContainer } from '@react-navigation/native';
import { configureStore } from '@reduxjs/toolkit'; //Need to install @reduxjs/toolkit
const render = (
ui,
{
preloadedState,
store = configureStore({
reducer: rootReducer,
preloadedState,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: false,
}),
}),
...renderOptions
} = {}
) => {
const Wrapper = ({ children }) => {
return (
<Provider store={store}>
<NavigationContainer>{children}</NavigationContainer> //Adding nav container here
</Provider>
);
};
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
};
// re-export everything
export * from '@testing-library/react-native';
// override render method
export { render };
Then, on a per test basis, you pass preloaded state to the render function of the test. This way every test can have a seperate redux testing environment.
it('Displays the locaiton', async () => {
const preloadedState = { entries: { allEntries: myPreloadedStateForThisTest } }; // Make sure to match the data structure of your real state.
const { findByText } = render(<EntriesScreen />, { preloadedState });
// Expect:
await findByText(/West Virginia/i);
});
Thanks @markerikson for the examlple code
Upvotes: 1
Reputation: 178
Redux is an object that defines the current state of the application. So unless you know the correct order of the jests it won't work. By the way, Jest doesn't run the tests in the same order every time. Sometimes it stores the ones that failed and runs them first the next time.
What it's recommended is that you step up the Redux store for each test/test file.
This is my test/utils/mocks/redux/index.js
import configureStore from 'redux-mock-store';
import createMockBluetooth from '../bt';
import createMockApi from '../api';
import createMockState from './state';
...
const createMockStore = ({
state = createMockState(),
api = createMockApi(),
ble = createMockBluetooth(),
...,
middlewares = [],
} = {}) => {
const thunkMiddleware = ({ dispatch, getState }) => (next) => (action) => (
typeof action !== 'function' ?
next(action) :
action(dispatch, getState, { api, bt, ... })
);
return configureStore([thunkMiddleware, ...middlewares])(state);
};
export default createMockStore;
This way it allows me to define a different store for each test:
addressList.test.js
const defaultProps = {
navigation: {
state: { params: {} },
dispatch: jest.fn(),
...
pop: jest.fn(),
popToTop: jest.fn(),
isFocused: jest.fn(),
},
};
const getStoreWithMockAddresses = (mockData) =>
createMockStore({
state: createMockState({
listOfAddresses: mockData,
},
{
customMerge: preferOverride('listOfAddresses'),
}),
});
....
test('random test', () => {
const mockAddresses = {
fullList: [
{ name: 'address-1', number: 69 },
],
};
const mockStore = getStoreWithMockAddresses(mockAddresses);
const { getByTestId } = render(<YourComponent navigation={ defaultProps.navigation } />, { wrapper: { store: mockStore } });
}
I do need to set up for each test. It makes sense since I'm testing of the components will work given a specific state.
Please note that the render
method syntax is a bit of since I'm using a custom implementation of the original render.
EDIT:
import merge from 'deepmerge';
const createMockState = (overrides = {}, options = []) => merge({
...
listOfAddresses: {
fullList: [] },
}, overrides, options);
export default createMockState;
Upvotes: 2