使用 Mocha、Chai 和 Sinon 完成承诺后测试非承诺

Testing non-promises after promise completes with Mocha, Chai and Sinon

我在 Redux 中创建了一个小型学习项目,基本上从 documentation 复制了用于异步操作的中间件。现在我想为它写一些测试,以确保在我改变一些东西后它能正常工作。

为了编写我的测试,我使用了 Mocha,以及 chai、chai-as-promised 和 sinon。

我对 redux 没有任何问题,但我不确定如何测试。

来自中间件的相关代码:

export default function callApiMiddleware({ dispatch }) {
  return next => action => {
    // ... various checks

    const [ requestType, successType, errorType ] = types;
    dispatch({ ...payload, type: requestType });

    return callApi().then(
      response => dispatch({ ...payload, type: successType, response }),
      error => dispatch({ ...payload, type: errorType, error })
    );
  }
}

无论承诺是履行还是拒绝,中间件都会调度适当的操作。

为了测试这一点,我与其他函数一起删除了调度函数,并且我还传递了一个虚拟承诺,我根据我想要测试的内容履行或拒绝。

我写的测试是这样的:

it('should trigger success action when successful', () => {
  let promise = callApiMiddleware({ dispatch, getState })(next)({
    ...action,
    callApi: () => new Promise((resolve, reject) => resolve('SUCCESS'))
  });
  return expect(promise).to.eventually.be.fulfilled;
});

这工作正常,但我 运行 遇到的第一个问题是当我试图模拟一个可能因没有互联网连接而导致的被拒绝的承诺时,所以我写了这个测试:

it('should trigger failure action when failure occurs', () => {
  let promise = callApiMiddleware({ dispatch, getState })(next)({
    ...action,
    callApi: () => new Promise((resolve, reject) => reject('ERROR'))
  });
  return expect(promise).to.eventually.be.rejected;
});

但是失败并显示以下消息:

AssertionError: expected promise to be rejected but it was fulfilled with undefined

Expected :[undefined]

Actual :[undefined]

这对我来说毫无意义,因为我清楚地传递了一个唯一的功能就是被拒绝的承诺。

我的另一个问题是我还想做出其他断言,这些断言不一定与承诺本身有任何关系,但必须在承诺完成时进行评估,例如我想断言 dispatch 方法被调用了两次。 dispatch 方法本身在使用 sinon 的测试中被删除,以执行所需的断言。潜在地,我想以这种方式做出多个断言。

我试过以下方法:

it('should trigger success action when successful', () => {
  let promise = callApiMiddleware({ dispatch, getState })(next)({
    ...action,
    callApi: () => new Promise((resolve, reject) => resolve('SUCCESS'))
  });
  return Q.all([
    expect(promise).to.eventually.be.fulfilled,
    expect(dispatch).to.eventually.be.calledTwice
  ]);
});

但是这个 returns 一些非常大的错误只是告诉我 dispatch 不是 thenable 即不是承诺。

我不知道如何做到这一点,所以任何意见将不胜感激。

Promise 不会重新抛出错误,因此如果您在第一个 catch 处理程序中捕获到错误,promise 将在下一个处理程序中实现,除非您再次抛出捕获到的错误。

// this does not work
promise.catch((e) => console.log(e)).catch((e) => console.log(e));

如果你想让它工作,你必须重新抛出一个错误。

promise.catch((e) => {
   console.log(e);
   throw e;
}).catch((e) => console.log(e));

如果你想让测试通过,你需要重新抛出在承诺的捕获处理程序中捕获的错误。所以你的代码应该是这样的:

export default function callApiMiddleware({ dispatch }) {
  return next => action => {
    // ... various checks

    const [ requestType, successType, errorType ] = types;
    dispatch({ ...payload, type: requestType });

    return callApi().then(
      response => dispatch({ ...payload, type: successType, response }),
      error => {
          dispatch({ ...payload, type: errorType, error });
          throw error;
      }
    );
  }
}

你得到一个由 undefined 实现的 Promise 的原因是因为这就是中间件 returns:

return callApi().then(
  response => dispatch({ ...payload, type: successType, response }),
  error => dispatch({ ...payload, type: errorType, error })
);

由于它没有在第二次回调中重新抛出错误,因此生成的 Promise 已实现。因为它也没有 return 任何东西,所以它用 undefined.

来满足

在这种情况下,您可以更改代码以重新抛出错误:

return callApi().then(
  response => dispatch({ ...payload, type: successType, response }),
  error => {
    dispatch({ ...payload, type: errorType, error })
    throw error;
  }
);

这将给出您期望的结果,但每次您的请求失败时都会在 DevTools 中报告未处理的拒绝。这对你来说可能没问题。

关于你的第二个例子:

But this returns some very large error that simply tells me that dispatch is not thenable i.e. not a promise.

看起来像.to.eventually.*works on Promises。事实上, dispatch 不是 Promise,所以你不能这样使用它。你可能想写这样的东西:

return expect(promise).to.eventually.be.fulfilled.then(() => {
  expect(dispatch).to.be.calledTwice();
});

最后,我鼓励您查看 Redux Saga. Describing side effects with generators can be easier than with a custom middleware, and generators are