2

Hey guys I am trying to test my async actions creators with Typescript but I am getting a type error that I cannot manage to solve.

This is my actions:

export const loadCurrentUserRequest = () => ({
  type: LOAD_CURRENT_USER_REQUEST
})

export const loadCurrentUserSuccess = (payload: any) => ({
  type: LOAD_CURRENT_USER_SUCCESS,
  payload
})

export const loadCurrentUserFailure = (payload: any) => ({
  type: LOAD_CURRENT_USER_FAILURE,
  payload
})

And this is my async action creator:

export const loadCurrentUser = () => {
  return async (dispatch: Dispatch<any>) => {
    dispatch(loadCurrentUserRequest())
    try {
      const response = await get(`api/currentuser`)
      if (!response.ok) {
        dispatch(loadCurrentUserFailure({ type: null, message: 'error' }))
      } else {
        const json = await response.json()
        dispatch(loadCurrentUserSuccess(json))
      }
      return response
    } catch (err) {
      dispatch(loadCurrentUserFailure({ type: err.name, message: err.message }))
      logError(err.name, err.message)
      return err
    }
  }
}

The 'get' function is a middleware I've created to handle 'fetch' GET call (it adds some stuff into the header etc).

This is my test:

  it('create an action to load the current user', () => {
    const middlewares = [thunk]
    const mockStore = configureMockStore(middlewares)
    const store = mockStore()
    const expectActions = [{ type: LOAD_CURRENT_USER_REQUEST }]

    store.dispatch(actions.loadCurrentUser())
    expect(store.getActions()).toEqual(expectActions)
  })

I am getting this error in my console:

Argument of type '(dispatch: Dispatch<any>) => Promise<any>' is not assignable to parameter of type 'AnyAction'.
  Property 'type' is missing in type '(dispatch: Dispatch<any>) => Promise<any>' but required in type 'AnyAction'.

I am not sure what I have done wrong here, I looked at the redux example way to test async action creators and this is similar. I can't figure where my issue is coming from.

I do know that I will have to mock my fetch API calls and update my expectActions variable but the code isn't compiling because of that..

1 Answer 1

3

Here is the solution:

index.ts:

import fetch from 'node-fetch';
import { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';

const logError = (name, message) => console.error(`name: ${name}, message: ${message}`);
const get = url => fetch(url);

export const LOAD_CURRENT_USER_REQUEST = 'LOAD_CURRENT_USER_REQUEST';
export const LOAD_CURRENT_USER_SUCCESS = 'LOAD_CURRENT_USER_SUCCESS';
export const LOAD_CURRENT_USER_FAILURE = 'LOAD_CURRENT_USER_FAILURE';

export const loadCurrentUserRequest = () => ({
  type: LOAD_CURRENT_USER_REQUEST
});

export const loadCurrentUserSuccess = (payload: any) => ({
  type: LOAD_CURRENT_USER_SUCCESS,
  payload
});

export const loadCurrentUserFailure = (payload: any) => ({
  type: LOAD_CURRENT_USER_FAILURE,
  payload
});

export const loadCurrentUser = () => {
  return async (dispatch: ThunkDispatch<any, any, AnyAction>) => {
    dispatch(loadCurrentUserRequest());
    try {
      const response = await get(`api/currentuser`);
      if (!response.ok) {
        dispatch(loadCurrentUserFailure({ type: null, message: 'error' }));
      } else {
        const json = await response.json();
        dispatch(loadCurrentUserSuccess(json));
      }
      return response;
    } catch (err) {
      dispatch(loadCurrentUserFailure({ type: err.name, message: err.message }));
      logError(err.name, err.message);
      return err;
    }
  };
};

Unit test:

import configureStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import * as actions from './';
import { AnyAction } from 'redux';
import fetch from 'node-fetch';

jest.mock('node-fetch', () => jest.fn());

const initialState = {};
type State = typeof initialState;
const middlewares = [thunk];
const mockStore = configureStore<State, ThunkDispatch<State, any, AnyAction>>(middlewares);
const store = mockStore(initialState);

describe('action creators', () => {
  describe('#loadCurrentUser', () => {
    afterEach(() => {
      store.clearActions();
    });
    it('load current user success', async () => {
      const userMocked = { userId: 1 };
      (fetch as jest.MockedFunction<any>).mockResolvedValueOnce({
        ok: true,
        json: jest.fn().mockResolvedValueOnce(userMocked)
      });
      await store.dispatch(actions.loadCurrentUser());
      expect(fetch).toBeCalledWith('api/currentuser');
      expect(store.getActions()).toEqual([
        { type: actions.LOAD_CURRENT_USER_REQUEST },
        { type: actions.LOAD_CURRENT_USER_SUCCESS, payload: userMocked }
      ]);
    });

    it('load current user failed', async () => {
      (fetch as jest.MockedFunction<any>).mockResolvedValueOnce({ ok: false });
      await store.dispatch(actions.loadCurrentUser());
      expect(fetch).toBeCalledWith('api/currentuser');
      expect(store.getActions()).toEqual([
        { type: actions.LOAD_CURRENT_USER_REQUEST },
        {
          type: actions.LOAD_CURRENT_USER_FAILURE,
          payload: {
            type: null,
            message: 'error'
          }
        }
      ]);
    });

    it('load current user failed when fetch error', async () => {
      (fetch as jest.MockedFunction<any>).mockRejectedValueOnce(new Error('fetch error'));
      await store.dispatch(actions.loadCurrentUser());
      expect(fetch).toBeCalledWith('api/currentuser');
      expect(store.getActions()).toEqual([
        { type: actions.LOAD_CURRENT_USER_REQUEST },
        {
          type: actions.LOAD_CURRENT_USER_FAILURE,
          payload: {
            type: 'Error',
            message: 'fetch error'
          }
        }
      ]);
    });
  });
});

Unit test result with 100% coverage:

 PASS  src/stackoverflow/54693637/index.spec.ts
  action creators
    #loadCurrentUser
      ✓ load current user success (8ms)
      ✓ load current user failed (1ms)
      ✓ load current user failed when fetch error (8ms)

  console.error src/stackoverflow/54693637/index.ts:3541
    name: Error, message: fetch error

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        3.32s, estimated 5s

Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/54693637

Sign up to request clarification or add additional context in comments.

1 Comment

Extremely helpful. I know that's not stackoverflow recommend comment. But review several other examples - this has been the most precise.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.