测试自定义redux中间件,它处理ajax请求

时间:2017-02-22 20:22:12

标签: ajax testing redux middleware

我正在使用自定义redux中间件构建react-redux应用程序。 在我的项目定义中,action仅提供了一个对象来定义中间件和reducer的操作类型和必要参数。所有的ajax请求都将由中间件处理。这是生命周期看起来像: 行动 - >中间件(如果拦截了动作) - >减速机 - >存储

当用户尝试登录时,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
  }
}

然后中间件将使用在操作中传递的参数来发送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中所做的是改变系统状态。如果状态为true,则前端将呈现信息页面。如果状态为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);
});

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

1 个答案:

答案 0 :(得分:0)

您的代码运行正常,但由于使用nock立即生成远程resquest响应,因此您不需要setTimeout这么长时间。问题是承诺排队微任务,它们只在完成 macrotask 之后(在您的情况下,it()),在同一个事件循环中运行。

这就是为什么你需要setTimeout排队另一个macrotask,时间没有区别。我相信setImmediate也应该有用。