Design Converter
Education
Last updated on Jun 13, 2024
Last updated on Jun 13, 2024
Redux mock store is a library designed to test the asynchronous and synchronous actions of a Redux store without interacting with the actual store. It provides a mock store for your tests with the same API as a real Redux store, but with simplified capabilities that allow you to assert on the actions that have been dispatched. This is particularly useful for unit tests where you want to ensure that the correct actions are being dispatched under certain conditions.
1import configureMockStore from 'redux-mock-store'; 2const mockStore = configureMockStore(); 3const store = mockStore({ myState: 'sample state' });
Using a redux mock store simplifies the testing process by allowing developers to focus on the behavior of the actions and reducers without worrying about the state changes in the actual redux store. It also helps in ensuring that components are correctly dispatching actions and that the store is updated as expected.
To start writing tests with redux mock store, you need to import react, the testing library, and other necessary libraries. These imports set the stage for creating a mock store and writing your tests.
1import React from 'react'; 2import { Provider } from 'react-redux'; 3import configureMockStore from 'redux-mock-store';
To install the redux-mock-store library, you can use npm or yarn. This will add the library to your project's dependencies, allowing you to create a configured mock store.
1npm install redux-mock-store --save-dev
To configure the mock store, you typically create a function that initializes it with the desired middlewares and initial state. This configured mock store can then be used throughout your test files.
1import configureMockStore from 'redux-mock-store'; 2import thunk from 'redux-thunk'; 3 4const middlewares = [thunk]; 5const mockStore = configureMockStore(middlewares); 6 7const initialState = {}; 8const store = mockStore(initialState);
When you need a new store instance for a test, you can call the mock store function with an optional initial state. This instance will be used to dispatch actions and test the state of the store.
1const store = mockStore({ myState: 'initial state' });
To mock Redux store in the React Testing Library, you can use the Provider component to wrap your react components. This allows you to pass the mock store instance as the store prop, ensuring that the component has access to the store during the test.
1import { render } from '@testing-library/react'; 2import { Provider } from 'react-redux'; 3 4const store = mockStore({ myState: 'initial state' }); 5 6const customRender = (ui, options) => 7 render(ui, { wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, ...options }); 8 9// Use customRender instead of render in your tests
Unit testing reducers with a mock store involves dispatching actions and checking if the reducer updates the state as expected. This is a straightforward process since reducers are pure functions.
1import reducer from 'myReducer'; 2import * as types from 'actionTypes'; 3 4describe('reducer', () => { 5 it('should handle SOME_ACTION', () => { 6 expect( 7 reducer({}, { 8 type: types.SOME_ACTION, 9 payload: 'some data', 10 }) 11 ).toEqual({ ...expectedState }); 12 }); 13});
For a complex test combining actions, you can dispatch multiple actions to the mock store and then assert the final state. This type of integration test verifies that the action creators and reducers work together correctly.
1describe('complex actions', () => { 2 it('should execute multiple actions', () => { 3 const store = mockStore({ myState: 'initial' }); 4 5 store.dispatch(someActionCreator()); 6 store.dispatch(anotherActionCreator()); 7 8 const actions = store.getActions(); 9 expect(actions).toEqual([...expectedActions]); 10 expect(store.getState()).toEqual(expectedFinalState); 11 }); 12});
Creating your own custom render function allows you to include additional context providers or setup that is common across multiple tests. This function wrapper helps to avoid repetitive code and maintain consistency.
1import { render as rtlRender } from "@testing-library/react"; 2import { createStore } from "redux"; 3import { Provider } from "react-redux"; 4// import your reducers and initial state 5import rootReducer from "./reducers"; 6import { initialState } from "./store"; 7 8function render( 9 ui, 10 { 11 initialState, 12 store = createStore(rootReducer, initialState), 13 ...renderOptions 14 } = {} 15) { 16 function Wrapper({ children }) { 17 return <Provider store={store}>{children}</Provider>; 18 } 19 return rtlRender(ui, { wrapper: Wrapper, ...renderOptions }); 20} 21 22// re-export everything 23export * from "@testing-library/react"; 24// override render method 25export { render };
With this custom render function, you can now easily render your react components in a testing environment that includes your redux store. This is particularly useful when you have a connected component that relies on the redux store state or dispatches actions.
For example, when testing a connected component, you can use your own custom render function like this:
1import { render, screen } from "my-test-utils"; 2// assuming you exported your custom render function as 'render' 3import MyConnectedComponent from "./MyConnectedComponent"; 4 5test("renders with redux", () => { 6 render(<MyConnectedComponent />); 7 expect(screen.getByText(/some text/i)).toBeInTheDocument(); 8});
This setup ensures that your component is tested within the context of a redux Provider store, making it possible to test both the component's behavior and its interaction with the redux store.
To ensure that your react components have access to the mock store during testing, you can wrap them with the Provider component from react-redux. This provider store makes the Redux store available to any nested components that have been wrapped in the connect() function.
1import { Provider } from 'react-redux'; 2import { render } from '@testing-library/react'; 3import MyConnectedComponent from './MyConnectedComponent'; 4import configureMockStore from 'redux-mock-store'; 5 6const mockStore = configureMockStore(); 7const store = mockStore({ myState: 'initial state' }); 8 9const wrapper = ({ children }) => <Provider store={store}>{children}</Provider>; 10 11const { getByTestId } = render(<MyConnectedComponent />, { wrapper }); 12 13expect(getByTestId('component-id')).toBeTruthy();
Testing asynchronous actions requires a redux mock store configured with middleware like redux-thunk. This allows you to write tests that handle asynchronous logic within your action creators.
1import configureMockStore from 'redux-mock-store'; 2import thunk from 'redux-thunk'; 3import { fetchSomeData } from './actions'; 4 5const middlewares = [thunk]; 6const mockStore = configureMockStore(middlewares); 7 8it('creates SUCCESS when fetching data has been done', () => { 9 const expectedActions = [ 10 { type: 'REQUEST' }, 11 { type: 'SUCCESS', payload: { data: 'some data' } } 12 ]; 13 const store = mockStore({ data: [] }); 14 15 return store.dispatch(fetchSomeData()).then(() => { 16 expect(store.getActions()).toEqual(expectedActions); 17 }); 18});
When writing tests for thunk actions, you can use the async and await keywords to wait for the promise to resolve. This ensures that all the dispatched actions are accounted for in your tests.
1import configureMockStore from 'redux-mock-store'; 2import thunk from 'redux-thunk'; 3import * as actions from './actionCreators'; 4 5const middlewares = [thunk]; 6const mockStore = configureMockStore(middlewares); 7 8it('handles async action creators', async () => { 9 const store = mockStore({ data: null }); 10 const expectedActions = [ 11 { type: 'LOAD_DATA_REQUEST' }, 12 { type: 'LOAD_DATA_SUCCESS', payload: { data: 'some async data' } } 13 ]; 14 15 await store.dispatch(actions.loadData()); 16 expect(store.getActions()).toEqual(expectedActions); 17});
To verify that your redux mock store is dispatching actions as expected, you can use the getActions method. This method returns an array of all the actions that have been dispatched to the store instance during the test.
1it('should dispatch the correct actions', () => { 2 const store = mockStore({ data: [] }); 3 4 store.dispatch({ type: 'ACTION_ONE' }); 5 store.dispatch({ type: 'ACTION_TWO' }); 6 7 const actions = store.getActions(); 8 expect(actions).toEqual([{ type: 'ACTION_ONE' }, { type: 'ACTION_TWO' }]); 9});
The action log provided by the getActions method can be used to analyze the dispatched actions inside your tests. This allows you to ensure that the correct actions are being dispatched and in the right order.
1it('should dispatch actions in the correct order', () => { 2 const store = mockStore({ data: [] }); 3 4 store.dispatch({ type: 'FIRST_ACTION' }); 5 store.dispatch({ type: 'SECOND_ACTION' }); 6 7 const actions = store.getActions(); 8 expect(actions[0].type).toBe('FIRST_ACTION'); 9 expect(actions[1].type).toBe('SECOND_ACTION'); 10});
When using Redux Toolkit, you may need to mock an RTK query for your tests. This involves creating a mock store and dispatching the async thunk action created by the RTK query.
1import { configureStore } from '@reduxjs/toolkit'; 2import { usersApi } from './services/users'; 3 4const store = configureStore({ 5 reducer: { 6 [usersApi.reducerPath]: usersApi.reducer, 7 }, 8 middleware: (getDefaultMiddleware) => 9 getDefaultMiddleware().concat(usersApi.middleware), 10}); 11 12it('mocks an RTK query', async () => { 13 const result = await store.dispatch(usersApi.endpoints.getUser.initiate(1)); 14 expect(result.data).toEqual({ id: 1, name: 'John Doe' }); 15});
After each test, it's a good practice to clear the action log to ensure that subsequent tests start with a clean slate. The clearActions method resets the store's dispatched actions array inside, making it ready for a new test.
1it('clears the action log between tests', () => { 2 const store = mockStore({ data: [] }); 3 4 store.dispatch({ type: 'ACTION_ONE' }); 5 expect(store.getActions()).toEqual([{ type: 'ACTION_ONE' }]); 6 7 store.clearActions(); 8 expect(store.getActions()).toEqual([]); 9});
When writing tests, it's important to structure your test code in a way that is easy to read and understand. Using describe blocks to group related tests and it or test for individual test cases can help organize your test code effectively.
1describe('actions', () => { 2 describe('action creators', () => { 3 it('should create an action to add a todo', () => { 4 const expectedAction = { 5 type: 'ADD_TODO', 6 text: 'Finish docs' 7 }; 8 expect(actions.addTodo('Finish docs')).toEqual(expectedAction); 9 }); 10 }); 11});
It's crucial to keep your tests focused on the action related logic. This means that each test should only assert the outcomes of specific actions or sequences of actions, rather than testing multiple pieces of logic at once.
1it('should handle adding a todo', () => { 2 const store = mockStore({ todos: [] }); 3 4 store.dispatch(addTodo('Run tests')); 5 6 const actions = store.getActions(); 7 const expectedPayload = { type: 'ADD_TODO', text: 'Run tests' }; 8 expect(actions).toEqual([expectedPayload]); 9});
When a test fails, you may need to inspect the state and dispatched actions to understand what went wrong. Logging the state and actions can provide valuable insights during the debugging process.
1it('should update the state correctly', () => { 2 const store = mockStore({ items: [] }); 3 4 store.dispatch(addItem('New Item')); 5 console.log(store.getState()); // Log the state for debugging 6 console.log(store.getActions()); // Log the dispatched actions 7 8 expect(store.getState().items).toContain('New Item'); 9});
Common issues with using redux mock store in tests include actions not being dispatched as expected or the state not updating correctly. To troubleshoot, ensure that your mock store is properly configured and that you're using the correct assertions.
1it('should dispatch actions correctly', () => { 2 const store = mockStore({ loading: false }); 3 4 store.dispatch(startLoading()); 5 store.dispatch(stopLoading()); 6 7 const expectedActions = [ 8 { type: 'START_LOADING' }, 9 { type: 'STOP_LOADING' } 10 ]; 11 12 expect(store.getActions()).toEqual(expectedActions); 13});
By following these guidelines and using the provided code examples, you can effectively use redux mock store to test your Redux logic. Remember to keep your tests focused, structured, and maintain a clear separation between testing different parts of your Redux code. With practice, you'll become proficient at writing tests that help ensure the reliability and maintainability of your Redux applications.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.