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

IT技术 javascript reactjs generator redux redux-saga
2021-05-12 03:40:53

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

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

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

我找了一个例子,我发现成功的传奇故事和失败,但在购物车示例故障情况下从来没有测试,例如这里

佐贺

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

测试文件

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
    });
  });
});
3个回答

马克的回答是正确的。中间件执行这些指令。但这会让你的生活更轻松:在测试中,你可以提供任何你想要的参数作为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' }
    })
  );
});

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

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

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