How to test API request failures with Redux Saga? How to test API request failures with Redux Saga? reactjs reactjs

How to test API request failures with Redux Saga?


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' }    })  );});


Correct - as I understand it, the whole point of Redux-Saga is that your saga function uses the saga APIs to return objects describing the action, and then the middleware later looks at those objects to actually execute the behavior. So, a yield call(myApiFunction, "/someEndpoint", arg1, arg2) statement in a saga might return an object that notionally looks like {effectType : CALL, function: myApiFunction, params: [arg1, arg2]}.

You can either inspect the redux-saga source to see exactly what those declarative objects actually look like and create a matching object to compare against in your test, or use the API functions themselves to create the objects (which is I think what redux-saga does in their test code).


You also might want to use a helper library to test your Sagas, such as redux-saga-testing.

Disclaimer: I wrote this library to solve that exact same problem

This library will make your test look like any other (synchronous) test, which is a lot easier to reason about than calling generator.next() manually.

Taking your example, you could write tests as follow:

(it's using Jest syntax, but it's essentially the same with Mocha, it's completely test library-agnostic)

import sagaHelper from 'redux-saga-testing';import { call, put } from 'redux-saga/effects';import actions from './my-actions';import api from './your-api';// Your exampleexport function* login(action) {    try {        const user = yield call(api.login, action);        return yield put(actions.loginSuccess(user));    } catch(e) {        yield put(actions.loginFail(e.message)); // Just changed that from "e" to "e.message"    }}describe('When testing a Saga that throws an error', () => {    const it = sagaHelper(login({ type: 'LOGIN', payload: 'Ludo'}));    it('should have called the API first, which will throw an exception', result => {        expect(result).toEqual(call(api, { type: 'LOGIN', payload: 'Ludo'}));        return new Error('Something went wrong');    });    it('and then trigger an error action with the error message', result => {        expect(result).toEqual(put(actions.loginFail('Something went wrong')));    });});describe('When testing a Saga and it works fine', () => {    const it = sagaHelper(login({ type: 'LOGIN', payload: 'Ludo'}));    it('should have called the API first, which will return some data', result => {        expect(result).toEqual(call(api, { type: 'LOGIN', payload: 'Ludo'}));        return { username: 'Ludo', email: 'ludo@ludo.com' };    });    it('and then call the success action with the data returned by the API', result => {        expect(result).toEqual(put(actions.loginSuccess({ username: 'Ludo', email: 'ludo@ludo.com' })));    });});

More examples (using Jest, Mocha and AVA) on GitHub.