Ludo Ludo - 1 month ago 11
React JSX Question

How to test API request failures with Redux Saga?

I am trying to test every scenarios my saga could follow, but i am not able to make happens the behaviors i want.
This is pretty simple, i have a HTTP request (login), and i want to test the success and the failure cases by mocking my API method.

But, it looks like the

call effect
doesn't fire my api function, i don't really get yet how it works, but i guess that the middleware is in charge of invoking the function, and since i don't go though the store on my test, i can't get the result.

So my question is, how can you test your saga when you need to dispatch different actions (typically success or failure) next to your async call ?

I looked for an example, i found sagas with success and fail but the fail case is never tested, for example in the shopping cart example here

SAGA.JS

export function* login(action) {
try {
const user = yield call(api.login, action);
return yield put(actions.loginSuccess(user));
} catch(e) {
yield put(actions.loginFail(e));
}
}

export default function* rootAuthenticationSagas() {
yield* takeLatest(LOGIN, login);
}


TEST.JS

describe('login', () => {
context('When it fails', () => {
before('Stub the api', () => {
sinon.stub(api, 'login', () => {
// IT NEVER COMES HERE !
return Promise.reject({ error: 'user not found' });
});
});

it('should return a LOGIN_FAIL action', () => {
const action = {
payload: {
name: 'toto',
password: '123456'
}
};
const generator = login(action);

// THE CALL YIELD
generator.next();

const expectedResult = put({ type: 'LOGIN_FAIL', payload: { error: 'user not found' } });
expect(generator.next().value).to.be.eql(expectedResult); // FAIL BECAUSE I GET A LOGIN_SUCCESS INSTEAD OF A FAIL ONE
});
});
});

Answer

Mark’s answer is correct. Middleware executes those instructions. But this makes your life easier: in the test, you can provide whatever you want as the argument to next(), and the generator function will receive it as a result of yield. This is exactly what saga middleware does (except that it actually fires up a request instead of giving you a fake response).

To make yield get an arbitrary value, pass it to next(). To make it “receive” an error, pass it to throw(). In your example:

it('should return a LOGIN_FAIL action', () => {
  const action = {
    payload: {
      name: 'toto',
      password: '123456'
    }
  };
  const generator = login(action);

  // Check that Saga asks to call the API
  expect(
    generator.next().value
  ).to.be.eql(
    call(api.login, action)
  );

  // Note that *no actual request was made*!
  // We are just checking that the sequence of effects matches our expectations.

  // Check that Saga reacts correctly to the failure
  expect(
    generator.throw({
      error: 'user not found'
    }).value
  ).to.be.eql(
    put({
      type: 'LOGIN_FAIL',
      payload: { error: 'user not found' }
    })
  );
});
Comments