我正在努力测试这个redux传奇

时间:2019-04-17 18:58:46

标签: testing redux react-redux redux-saga

我对redux-saga还是比较陌生,并且正在努力测试以下代码:

import { normalize } from 'normalizr';

export function* normalizeResponse(denormalized, schema) {
  const normalized = yield call(normalize, denormalized, schema);
  return normalized;
}

export function* request(apiFn, action, schema) {
  try {
    yield put(requestStart({ type: action.type }));
    const denormalized = yield call(apiFn, action.payload, action.meta);
    const normalized = yield call(normalizeResponse, denormalized, schema);
    yield put(requestSuccess({ type: action.type }));
    return normalized;
  } catch (e) {
    if (__DEV__ && !__TEST__) {
      Alert.alert('Something went wrong');
      console.log(`Error in request saga: ${action.type}`, e);
    }
    if (action.type) {
      const payload = { type: action.type, error: e };
      const meta = action.payload || {};
      yield put(requestFailure(payload, meta));
    }
  }
}

export function* photosShow() {
  while (true) {
    const action = yield take(t.PHOTOS_SHOW);
    const normalized = yield call(request, api.show, action, {
      photo: schema.photo,
    });
    if (normalized) yield put(setEntities(normalized));
  }
}

在线上,我发现了许多redux saga测试包和一些教程,但似乎没有一个涵盖的范围超过基础知识。这是传奇工作原理的分步说明:

  • photosShow以Flux标准动作调用,有效载荷为{id:1}
  • 这将调用生成器request,它是一个实用函数,用于发出API请求,然后对响应进行规范化。
  • 首先,将触发requestStart操作
  • 然后将api端点称为
  • 如果成功,则将触发requestSuccess操作
  • 然后将使用normalizr标准化响应
  • 然后使用setEntities以redux状态存储(返回photosShow)

我们将不胜感激。

1 个答案:

答案 0 :(得分:0)

我也很努力地阅读所有redux-saga测试资源……没有找到合适的解决方案(至少对我而言)。

我最终得到的是:

  • 我“渲染”具有工作商店的空React应用
  • 我手动触发了有趣的动作(触发我正在测试的sagas的动作)
  • 我监视了Sagas消耗的所有外部资源

最后:我触发了Sagas,并发现它们触发了其他东西。

我认为Sagas是一个黑匣子,我检查他们是否遵守与该应用程序所有其他部分的合同。

我以身份验证sagas测试为例(我打破了很多测试良好实践,我知道,这是由于我早期对saga的测试而来的)(请参见下文中的renderWithRedux和{{1} }函数):

spyUtil

逐步: -我使用正常的Redux + Saga商店渲染了一个空应用

describe("Login flow with valid credentials", () => {
  let user = "stefano";
  let pwd = "my_super_secret_password";
  let app;
  let spies;
  // const spiedConsole = spyConsole();
  beforeAll(() => {
    app = renderWithRedux(<></>);
    spies = {
      LOGIN_SUCCESS_creator: spyUtil(authActions, "LOGIN_SUCCESS_creator"),
      navigate: spyUtil(ReachRouter, "navigate"),
      postLogin: spyUtil(authNetwork, "postLogin", postLoginOk),
      redirectBackFromLoginPage: spyUtil(navigationData, "redirectBackFromLoginPage")
    };
  });
  test("1 - the login API should be called as soon as the LOGIN_REQUEST action is dispatched", async () => {
    app.store.dispatch(authActions.LOGIN_REQUEST_creator(user, pwd));
    expect(spies.postLogin.spy).toHaveBeenCalledWith(user, pwd);
  });
  test("2 - then when the login API is successfull, a LOGIN_SUCCESS action should be dispatched with the tokens", async () => {
    expect(spies.LOGIN_SUCCESS_creator.spy).toHaveBeenCalledWith(
      expect.any(String),
      expect.any(String)
    );
  });
  test("3 - then the router should be asked to make a redirect to the initial location", async () => {
    expect(spies.redirectBackFromLoginPage.spy).toHaveBeenCalled();
    expect(spies.navigate.spy).toHaveBeenCalledWith(expect.stringMatching(/\//));
  });
  afterAll(() => {
    spies.values().forEach(obj => obj.spy.mockRestore());
    // spiedConsole.mockRestore();
    cleanup();
  });
});
  • 我监视除魔鬼之外的所有事物
app = renderWithRedux(<></>);

其中:

  • spies = { LOGIN_SUCCESS_creator: spyUtil(authActions, "LOGIN_SUCCESS_creator"), navigate: spyUtil(ReachRouter, "navigate"), postLogin: spyUtil(authNetwork, "postLogin", postLoginOk), redirectBackFromLoginPage: spyUtil(navigationData, "redirectBackFromLoginPage") }; 是动作创建者
  • LOGIN_SUCCESS_creator来自“到达路由器”
  • navigate发出AJAX请求(用假函数模拟它,几乎立即返回“成功”响应(但解决了承诺))
  • postLogin是在某些情况下再次使用redirectBackFromLoginPage实用程序的功能

    • 我触发了navigate动作,我希望已经使用正确的凭据调用了AJAX触发函数
LOGIN_REQUEST
  • 我检查test("1 - the login API should be called as soon as the LOGIN_REQUEST action is dispatched", async () => { app.store.dispatch(authActions.LOGIN_REQUEST_creator(user, pwd)); expect(spies.postLogin.spy).toHaveBeenCalledWith(user, pwd); }); 操作是否将与身份验证令牌一起分发
LOGIN_SUCCESS
  • 我检查路由器是否已通过正确的路由(首页test("2 - then when the login API is successfull, a LOGIN_SUCCESS action should be dispatched with the tokens", async () => { expect(spies.LOGIN_SUCCESS_creator.spy).toHaveBeenCalledWith( expect.any(String), expect.any(String) ); }); )被调用
/
  • 然后,我清除所有内容
test("3 - then the router should be asked to make a redirect to the initial location", async () => {
  expect(spies.redirectBackFromLoginPage.spy).toHaveBeenCalled();
  expect(spies.navigate.spy).toHaveBeenCalledWith(expect.stringMatching(/\//));
});




这是“我的”(来自Kent C. Dodds)afterAll(() => { spies.values().forEach(obj => obj.spy.mockRestore()); // spiedConsole.mockRestore(); cleanup(); }); 函数

renderWithRedux

其中// @see https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-redux.js export function renderWithRedux(ui, { initialState, store = configureStore() } = {}) { return { ...render( <div> <Provider store={store}>{ui}</Provider> </div> ), // adding `store` to the returned utilities to allow us // to reference it in our tests (just try to avoid using // this to test implementation details). store }; } 是我的功能,它使用各种中间件来构建整个Redux存储。

这是我的configureStore函数

spyUtil

请注意,这只是身份验证流程之一,我没有在这里报告所有情况。

我想知道你对此的看法