Search code examples
javascriptreactjsrecursionreduxredux-thunk

How to test a recursive dispatch in Redux


I implemented my own way to handle access/refresh token. Basically when accessToken is expired, it awaits the dispatch of another action and, if it is successful, it dispatch again itself. The code below explains it better:

export const refresh = () => async (dispatch) => {
  dispatch({
    type: REFRESH_USER_FETCHING,
  });

  try {
    const user = await api.refresh();

    dispatch({
      type: REFRESH_USER_SUCCESS,
      payload: user,
    });
    return history.push("/");
  } catch (err) {
    const { code } = err;
    if (code !== "ACCESS_TOKEN_EXPIRED") {
      dispatch({
        type: REFRESH_USER_ERROR,
        payload: err,
      });

      const pathsToRedirect = ["/signup"];

      const {
        location: { pathname },
      } = history;

      const path = pathsToRedirect.includes(pathname) ? pathname : "/login";
      return history.push(path);
    }

    try {
      await dispatch(refreshToken());
      return dispatch(refresh());
    } catch (subErr) {
      dispatch({
        type: REFRESH_USER_ERROR,
        payload: err,
      });
      return history.push("/login");
    }
  }
};

export const refreshToken = () => async (dispatch) => {
  dispatch({
    type: REFRESH_TOKEN_FETCHING,
  });

  try {
    await api.refreshToken();
    dispatch({
      type: REFRESH_TOKEN_SUCCESS,
    });
  } catch (err) {
    dispatch({
      type: REFRESH_TOKEN_ERROR,
      payload: err,
    });
  }
};

the issue is that I am finding it really difficult to test with Jest. In fact, I have implemented this test:

import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import * as actionCreators from "./actionCreators";
import * as actions from "./actions";
import api from "../../api";
jest.mock("../../api");

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe("authentication actionCreators", () => {
  it("runs refresh, both token expired, should match the whole flow", async () => {
    api.refresh.mockRejectedValue({
      code: "ACCESS_TOKEN_EXPIRED",
      message: "jwt expired",
    });

    api.refreshToken.mockRejectedValue({
      code: "REFRESH_TOKEN_EXPIRED",
      message: "jwt expired",
    });

    const expectedActions = [
      { type: actions.REFRESH_USER_FETCHING },
      { type: actions.REFRESH_TOKEN_FETCHING },
      { type: actions.REFRESH_TOKEN_ERROR },
      { type: actions.REFRESH_USER_ERROR },
    ];
    const store = mockStore({ auth: {} });

    await store.dispatch(actionCreators.refresh());

    expect(store.getActions()).toEqual(expectedActions);
  });
});

but instead of completing, the test runs indefenitely. This issue is not happening when I am testing it manually, so I think there is something missing in Jest, so my question is: is there a way to test this recursive behaviour?

Thanks


Solution

  • The problem is await you use with dispatch, dispatch returns an action, not a Promise, use Promise.resolve instead.