Search code examples
reactjsreact-nativeredux-sagaredux-saga-test-plan

How to handle errors in saga testing?


I am trying to test the below saga, but it keeps failing, I think my main issue is mocking up the result, error msg & token status in race effect.

Saga

export function* handleFetchUser(action) {
  const token = yield select(getToken);
  try{

  const { result } = yield race({
    result: call(fetchUser, url),
    timeout: delay(1000),
  });
 if(result){
     yield call(action.success, result, action);
 }
 }
  catch(e){
    if(e.message === 401 && token.status === 'logout'){
      yield call(logoutUser, action);
    }else if(e.message === 500 && token.status){
       yield call(action.error);
    }
}
}

Test Success:

it('should fetch users', () => {
const action = {
  type: "Get Users",
  onSuccess: jest.fn(),
  onError: jest.fn(),
};

return expectSaga(handleFetch, action)
      .provide({
        select: tokenSelector,
        race: () => ({ result: mockResponse }),
      })
      .call(action.onSuccess, mockResponse, action)
      .dispatch(action)
      .not.call(action.onError)
      .run();
  });

Test Error:

it('should request logout user on error 401', () => {
const action = {
  type: 'Get User data',
};

return expectSaga(handleFetchUser, action)
  .provide({
    race: () => {
      throw new Error(401);
    },
    select: ({ selector }, next) => {
      if (selector === getToken) {
        return undefined;
      }
      return next();
    },
    call: ({ fn }, next) => {
      if (fn === logoutUser) {
        return jest.fn();
      }
      return next();
    },
  })
  .select(getToken)
  .call(logoutUser, action)
  .dispatch(action)
  .run();
})

For the success, its not able to get result/response from the race. For the error, once, I removed the if else part in the catch, it works fine, the logoutUser call pass.


Solution

  • From what I can tell there seem to be few small issues,

    1. In the success test, you are using onSuccess and onError in the action, however the saga implementation expects success and error properties instead.

    2. In the error test, you are defining your token selector as:

    select: ({ selector }, next) => {
      if (selector === getToken) {
        return undefined;
      }
      return next();
    }
    

    Which means, that the getToken function will return undefined and so the token.status === 'logout' won't pass. So you probably want to have this instead:

    select: ({selector}, next) => {
      if (selector === getToken) {
        return {
          status: 'logout',
        }
      }
      return next()
    },
    
    1. Another issue is the e.message === 401 condition. I believe error message will always be a string and so even if you do throw new Error(401) in your test, it will be converted to string. So you should modify your saga to expect a string:
    e.message === '401'
    

    With 2 & 3 fixed the error condition should pass and the logout function will get called.