【问题标题】:How to test API request failures with Redux Saga?如何使用 Redux Saga 测试 API 请求失败?
【发布时间】:2016-06-09 19:45:11
【问题描述】:

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

但是,看起来call effect 没有触发我的 api 函数,我还没有真正了解它是如何工作的,但我猜中间件负责调用该函数,因为我没有我的测试不要去商店,我不能得到结果。

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

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

【问题讨论】:

    标签: javascript reactjs generator redux redux-saga


    【解决方案1】:

    您可能还想使用帮助程序库来测试您的 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)。

    【讨论】:

      【解决方案2】:

      Mark’s answer 是正确的。中间件执行这些指令。但这会让你的生活更轻松:在测试中,你可以提供 whatever you want 作为next() 的参数,生成器函数将作为yield 的结果接收它。这正是 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' }
          })
        );
      });
      

      【讨论】:

        【解决方案3】:

        正确 - 据我了解,Redux-Saga 的全部意义在于您的 saga 函数使用 saga API 返回描述操作的对象,然后中间件稍后查看这些对象以实际执行行为。因此,saga 中的 yield call(myApiFunction, "/someEndpoint", arg1, arg2) 语句可能会返回一个看起来像 {effectType : CALL, function: myApiFunction, params: [arg1, arg2]} 的对象。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-11-21
          • 2018-06-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-07
          相关资源
          最近更新 更多