Search code examples
typescriptunit-testingjestjsautomated-testsredux-thunk

How to test thunk with redux-toolkit slice?


I have a simple THUNK:

import { AppDispatch } from "../store";
import { Api } from "../../shared/api/index";
import { registrationFormSlice } from "../registration";

export const logIn =
  (email: string, password: string) =>
  async (dispatch: AppDispatch) => {
    try {         
      const { status } = await Api.auth.logIn({ email, password });
      if (status === 200) {
        dispatch(registrationFormSlice.actions.reset());
      }          
    } catch (error: any) {
      console.error(error);
    }
  };

How can I test it?

I tried to find the answer on my own, but everything I could find did not give an answer or was very difficult to understand. I can't even write how I'm testing this THUNK as I can't figure out where to start.


Solution

  • If you only want to test the action-related logic, you can use redux-mock-store package.

    A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.

    Please note that this library is designed to test the action-related logic, not the reducer-related one.

    E.g.

    api.ts:

    const Api = {
      auth: {
        async logIn(payload) {
          return fetch('http://localhost:3000/api/login')
        }
      }
    }
    
    export { Api }
    

    registration.ts:

    import { createSlice } from '@reduxjs/toolkit';
    
    const registrationFormSlice = createSlice({
      name: 'registration',
      initialState: {},
      reducers: {
        reset() { }
      }
    })
    
    export { registrationFormSlice }
    

    thunk.ts:

    import { Api } from "./api";
    import { registrationFormSlice } from "./registration";
    
    type AppDispatch = any;
    
    export const logIn =
      (email: string, password: string) =>
        async (dispatch: AppDispatch) => {
          try {
            const { status } = await Api.auth.logIn({ email, password });
            if (status === 200) {
              dispatch(registrationFormSlice.actions.reset());
            }
          } catch (error: any) {
            console.error(error);
          }
        };
    

    thunk.test.ts:

    import { AnyAction } from 'redux';
    import configureStore, { MockStoreCreator } from 'redux-mock-store'
    import thunk, { ThunkDispatch } from 'redux-thunk'
    import { logIn } from './thunk';
    import { Api } from "./api";
    import { registrationFormSlice } from './registration';
    
    type RootState = any;
    
    const middlewares = [thunk]
    type DispatchExts = ThunkDispatch<RootState, undefined, AnyAction>
    const mockStoreCreator: MockStoreCreator<RootState, DispatchExts> =
      configureStore<RootState, DispatchExts>(middlewares)
    
    
    describe('thunk', () => {
      afterEach(() => {
        jest.restoreAllMocks();
      })
      test('should pass', () => {
        jest.spyOn(Api.auth, 'logIn').mockResolvedValue({ status: 200 } as unknown as Response)
        const store = mockStoreCreator();
        return store.dispatch(logIn('[email protected]', '123')).then(() => {
          const actions = store.getActions();
          expect(actions).toEqual([registrationFormSlice.actions.reset()])
        })
      })
    })
    

    Test result:

     PASS  stackoverflow/76302702/thunk.test.ts (19.876 s)
      thunk
        ✓ should pass (7 ms)
    
    -----------------|---------|----------|---------|---------|-------------------
    File             | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -----------------|---------|----------|---------|---------|-------------------
    All files        |   88.24 |       50 |      60 |    87.5 |                   
     api.ts          |   66.67 |      100 |       0 |   66.67 | 4                 
     registration.ts |     100 |      100 |       0 |     100 |                   
     thunk.ts        |   90.91 |       50 |     100 |      90 | 15                
    -----------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        21.879 s