如何使用 Redux Saga 测试 API 请求失败?

How to test API request failures with Redux Saga?

我正在尝试测试我的传奇可能遵循的每个场景,但我无法实现我想要的行为。 这很简单,我有一个 HTTP 请求(登录),我想通过模拟我的 API 方法来测试成功和失败案例。

但是,看起来 call effect 没有触发我的 api 函数,我还不太明白它是如何工作的,但我猜中间件负责调用功能,并且由于我在测试时没有去商店,所以我无法得到结果。

所以我的问题是,当您需要在异步调用旁边分派不同的操作(通常是成功或失败)时,如何测试您的 saga?

我找了一个例子,我找到了成功和失败的传奇,但从未测试过失败案例,例如在购物车示例中 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
    });
  });
});

正确 - 据我了解,Redux-Saga 的全部意义在于您的传奇功能使用传奇 APIs 到 return 描述操作的对象,然后中间件稍后看起来在那些对象上实际执行行为。因此,saga 中的 yield call(myApiFunction, "/someEndpoint", arg1, arg2) 语句可能 return 一个概念上看起来像 {effectType : CALL, function: myApiFunction, params: [arg1, arg2]}.

的对象

您可以检查 redux-saga 源代码以准确查看这些声明性对象的实际外观并创建匹配对象以在您的测试中进行比较,或者使用 API 函数本身来创建对象(我认为 redux-saga 在他们的测试代码中做了什么)。

是正确的。中间件执行这些指令。但这会让你的生活更轻松:在测试中,你可以提供 任何你想要的 作为 next() 的参数,生成器函数将接收它作为 [= 的结果12=]。这正是 saga 中间件所做的(除了它实际触发一个请求而不是给你一个虚假的响应)。

要让yield得到一个任意值,把它传给next()。要使其“接收”错误,请将其传递给 throw()。在您的示例中:

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

您可能还想使用辅助库来测试您的 Sagas,例如 redux-saga-testing

免责声明:我编写这个库是为了解决完全相同的问题

这个库将使您的测试看起来像任何其他(同步)测试,这比手动调用 generator.next() 更容易推理。

以你的例子为例,你可以编写如下测试:

(它使用 Jest 语法,但它与 Mocha 基本相同,它完全与测试库无关)

import sagaHelper from 'redux-saga-testing';
import { call, put } from 'redux-saga/effects';
import actions from './my-actions';
import api from './your-api';

// Your example
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.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' })));
    });
});

GitHub 上的更多示例(使用 Jest、Mocha 和 AVA)。