29

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

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

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

我找了一个例子,我发现 sagas 成功和失败,但失败案例从未测试过,例如在购物车示例

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

测试.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
    });
  });
});
4

3 回答 3

46

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

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

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

于 2016-02-26T19:00:40.020 回答
0

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

于 2016-10-19T08:37:46.313 回答