【问题标题】:testing custom redux middleware, which handles ajax request测试处理 ajax 请求的自定义 redux 中间件
【发布时间】:2017-02-22 20:22:12
【问题描述】:

我正在构建一个 react-redux 应用程序,使用自定义 redux 中间件。 在我的项目的定义中,action只提供了一个对象来定义中间件和reducer的action类型和必要的参数。所有的 ajax 请求都将由中间件处理。这是生命周期的样子: action -> 中间件(如果 action 被拦截) -> reducer -> store

当用户尝试登录时,react 组件上的操作会触发一个动作,如下所示:

export function login(username, password) {
  return {
    type: 'LOGIN',
    username: username,
    password: password
  }
}

export function authSucceed(username, isAdmin) {
  return {
    type: 'AUTHSUCCEED',
    username: username,
    isAdmin: isAdmin
  }
}

export function authFail(text) {
  return {
    type: 'AUTHFAIL',
    errorMessage: text
  }
}

然后中间件会使用action中传入的参数来发送ajax请求,大概是这样的。

export function customedMiddleware(store) {
  return next => action => {
    if (action.type === 'LOGIN') {
      axios.post(url + '/api/login', {
        username: action.username,
        password: action.password
      })
        .then(res => {
          if (res.status === 200) {
            store.dispatch(actions.authSucceed(res.data.username, res.data.isAdmin));
          } else {
            store.dispatch(actions.authFail(res.data));
          }
        })
        .catch(error => console.log(error));
      }
      return next(action);
    };
  }

中间件向服务器发送登录请求后,根据认证是否成功,中间件会相应地在reducer中派发一些动作。由于 authSucceed 和 authFail 不会被中间件拦截,reducer 会做相应的处理。

export default function(state = false, action) {
  switch(action.type) {
    case 'AUTHSUCCEED': 
      return true;
    case 'AUTHFAIL': 
        return false;
    case 'LOGOUT': 
        return false;
  }
  return state;
}

这里在 reducer 中所做的是改变系统状态。如果状态为真,前端会渲染信息页面。如果状态为false,则前端将停留在登录页面。

我喜欢这样的系统定义。每个 MVC 部分都很好地隔离。但是,测试中间件非常困难。目前,我正在以这种方式进行测试:

it('should dispatch authSucceed if signup with correct info', () => {
  nock('http://localhost:8080')
    .post('/api/signup', {
      username: 'bruce',
      password: 'Gx1234'
    })
    .reply(200, {
      username: 'bruce',
      isAdmin: false
    });

  const createStoreWithMiddleware = applyMiddleware(customedMiddleware)(createStore);
  const store = createStoreWithMiddleware(reducers);
  const dispatch = sinon.spy(store, 'dispatch');

  store.dispatch(actions.login('bruce', 'Gx1234'));
  setTimeout(() => {
    expect(dispatch.calledWith({
      type: 'AUTHSUCCEED',
      username: 'bruce',
      isAdmin: false
    })).to.be.true;
  }, 100);
});

我发送登录操作。然后窥探 authSucceed 动作和 authFail 动作是否会在 100 毫秒内正确调用。如果只有一个测试要运行,则此方法有效。如果按顺序运行不止一个测试,它们可能会相互影响。我必须调整 setTimeout 的时间延迟以使其适用于所有情况,即 10 毫秒。 这种方式我感觉不舒服。我无法确定它是否适合我或所有人,因为绝对时间与硬件有关。 如果有人能给我一些关于如何测试这个自定义中间件的建议,我将不胜感激。

【问题讨论】:

  • 如果你使用setTimeout(..., 0)会发生什么?
  • redux.js.org/docs/recipes/… 这是一个很好的资源。另外,为什么不使用 redux-thunk?
  • 实际上,setTimeout(..., 0) 有效。
  • 我尝试使用 redux-thunk。但是,我找到的参考资料使用 redux 来测试异步操作。那不是我的情况。我使用中间件来处理 ajax 请求和响应。但我会更深入地研究 thunk 以更好地理解它是如何工作的。感谢您的建议。

标签: ajax testing redux middleware


【解决方案1】:

您的代码运行良好,但您不应该需要这么长时间的setTimeout,因为使用 nock 会立即做出远程请求响应。问题是 promise 将 microtasks 排入队列,并且它们仅在 ma​​crotask 完成后运行(在您的情况下为 it()),在同一个事件循环中。

这就是为什么你需要setTimeout 来排队另一个宏任务,时间并不重要。我相信setImmediate 应该也可以。

【讨论】:

    猜你喜欢
    • 2017-05-05
    • 1970-01-01
    • 1970-01-01
    • 2013-07-24
    • 1970-01-01
    • 1970-01-01
    • 2016-04-15
    • 1970-01-01
    • 2021-03-11
    相关资源
    最近更新 更多