【问题标题】:How do i unit test promises using sinon, mocha我如何使用 sinon、mocha 对 Promise 进行单元测试
【发布时间】:2019-05-24 09:34:31
【问题描述】:

我有以下实现。我如何进行单元测试。我想确保 processData 在成功时只被调用一次,handleDecryptError 在错误时被调用一次。

我的单元测试向我抛出了一个错误,指出 AssertionError: expected 0 to equal 1 for should.equal(getHandleDecryptErrorSpy.callCount, 1);

实施:

let getData = exports.getData = (err, res, param) => {

    ...

    return security.decryptJWE(jweParts[0], jweParts[1], jweParts[2], jweParts[3], jweParts[4], privatekey)
        .then(data => processData(data, param))
        .catch(error => handleDecryptError(error, param));
}

let handleDecryptError = exports.handleDecryptError = (error, param) => {
    log.error(message.ERROR_DECRYPTING.red, error);

    ..

    return;
}

let processData = exports.processData = (momData, param) => {

    log.info(message.SUCCESS.red);

    ...

}

测试用例:

it('6. Response for data is empty string', (done) => {

        let getDecryptJWESpy = sinon.stub(security, "decryptJWE").rejects(message.ERROR_DECRYPTING);
        let getProcessDataSpy = sinon.spy(updateTask, "processData");
        let getHandleDecryptErrorSpy = sinon.spy(updateTask, "handleDecryptError");

        let response = {
            body: "",
            text: ""
        };

        updateTask.getData(null, response, ["TASKID"]);
        should.equal(getDecryptJWESpy.callCount, 1);
        should.equal(getProcessDataSpy.callCount, 0);
        should.equal(getHandleDecryptErrorSpy.callCount, 1);

        done();
    });

【问题讨论】:

  • 如果你的函数从security.decryptJWE()返回promise,测试起来会容易得多
  • @MarkMeyer 所以如果我兑现我的承诺,我该如何继续进行测试?
  • 这不仅仅是评论可以处理@shadow。请参阅下面的代码。

标签: node.js unit-testing promise mocha.js sinon


【解决方案1】:

如果您从updateTask.getData 返回承诺,您可以在测试中调用它并将断言放在附加到调用的then() 中。现在测试可以等待getData() 解决,然后再尝试任何断言:

it('6. Response for data is empty string', () => { // don't pass done

   return updateTask.getData() // return this promise and mocha will handle it
   .then(() => {
        // assertions will now run at the right time
        should.equal( /* etc */)
   }

另外,如果你没有通过done,你可以从测试中返回承诺,mocha 会知道测试何时完成。

此外,由于您使用的是 Sinon — Sinon 有一个不错的 assertion library,您可以使用如下:sinon.assert.calledOnce(getDecryptJWESpy);。这将为您提供比equal 更好的失败消息。

【讨论】:

  • 我是否以正确的方式履行我的承诺? let getDecryptJWESpy = sinon.stub(security, "decryptJWE").resolves(message.ERROR_DECRYPTING)
  • 无论如何我可以使用getProcessDataSpygetHandleDecryptErrorSpy 来测试then() 中的计数吗?显然,无论如何,它们似乎仍然是 0。
【解决方案2】:

我认为你需要做的就是等待承诺。使用更方便async/await

it('6. Response for data is empty string', async () => {

    let getDecryptJWESpy = sinon.stub(security, "decryptJWE").resolves(message.ERROR_DECRYPTING);
    let getProcessDataSpy = sinon.spy(updateTask, "processData");
    let getHandleDecryptErrorSpy = sinon.spy(updateTask, "handleDecryptError");

    let response = {
        body: "",
        text: ""
    };

    try {
        await updateTask.getData(null, response, ["TASKID"]);
    } catch(ignore) {}

    should.equal(getDecryptJWESpy.callCount, 1);
    should.equal(getProcessDataSpy.callCount, 0);
    should.equal(getHandleDecryptErrorSpy.callCount, 1);
});

【讨论】:

    【解决方案3】:

    您需要首先确定需要测试的内容以及测试用例何时完成。此外,由于您的代码使用回调而不是异步等待,因此使用 done() 更容易断言结果。

    成功后,测试 processData 是否仅使用所需的 args 调用一次

    调用processData时成功测试完成时

    因此你的测试用例变成了

    it('6. Response for data is empty string', (done) => {
      let getDecryptJWESpy = sinon.stub(security, "decryptJWE").resolves(message.ERROR_DECRYPTING);
      let getHandleDecryptErrorSpy = sinon.stub(updateTask, "handleDecryptError");
      let getProcessDataSpy = sinon.stub(updateTask, "processData").callsFake(function fakeFn() {
        should.equal(getDecryptJWESpy.callCount, 1);
        should.equal(getProcessDataSpy.callCount, 1);
        should.equal(getHandleDecryptErrorSpy.callCount, 0);
        //The test is done only is this point is reached
        done();
      });
    
      let response = {
        body: "",
        text: ""
      };
      updateTask.getData(null, response, ["TASKID"]);
    });
    

    失败时,测试handleDecryptError是否只被调用一次,需要的参数

    当调用 handleDecryptError 时失败测试完成时

    因此你的测试用例变成了

    it('6. Response for data is empty string', (done) => {
      let getDecryptJWESpy = sinon.stub(security, "decryptJWE").resolves(message.ERROR_DECRYPTING);
      let getProcessDataSpy = sinon.stub(updateTask, "processData");
      let getHandleDecryptErrorSpy = sinon.stub(updateTask, "handleDecryptError").callsFake(function fakeFn() {
        should.equal(getDecryptJWESpy.callCount, 1);
        should.equal(getProcessDataSpy.callCount, 0);
        should.equal(getHandleDecryptErrorSpy.callCount, 1);
        //The test is done only is this point is reached
        done();
      });
    
      let response = {
        body: "",
        text: ""
      };
      updateTask.getData(null, response, ["TASKID"]);
    });
    

    原样的代码可能不起作用,因为我没有测试过,但根据我的测试结构应该如上。

    【讨论】:

      【解决方案4】:

      我的第一个建议是在任何地方都使用回调或承诺保持一致。由于security.decryptJWE 使用了promise,我们将使getData 也使用promise。

      let getData = exports.getData = (err, res, param) => {
        ...
      
        // add 'return' so `getData` returns promise
        return security.decryptJWE(jweParts[0], jweParts[1], jweParts[2], jweParts[3], jweParts[4], privatekey)
            .then(data => processData(data, param))
            .catch(error => handleDecryptError(error, param));
      }
      

      您的测试用例似乎想测试是否调用了handleDecryptError,因此您必须使用rejects 而不是resolves。如果使用resolves,它将转到processData 而不是handleDecryptError

      it('6. Response for data is empty string', () => {
      
        let getDecryptJWESpy = sinon.stub(security, "decryptJWE").rejects(message.ERROR_DECRYPTING); // update to use rejects
        let getProcessDataSpy = sinon.spy(updateTask, "processData");
        let getHandleDecryptErrorSpy = sinon.spy(updateTask, "handleDecryptError");
      
        let response = {
            body: "",
            text: ""
        };
      
        return updateTask.getData(null, response, ["TASKID"]) // add 'return' and 'then'
          .then(() => { // alternatively, we can use `async/await`
            should.equal(getDecryptJWESpy.callCount, 1);
            should.equal(getProcessDataSpy.callCount, 0);
            should.equal(getHandleDecryptErrorSpy.callCount, 1);
          });
      });
      

      希望对你有帮助

      【讨论】:

      • 它仍然抛出与我收到的相同的错误。 AssertError: expected handleDecryptError to be called once but was called 0 times
      • @shadow 有意思,你把resolves 改成rejects了吗?
      • 是的。我做到了,这就是为什么我仍在思考哪里出了问题。有什么我可能错过的吗?
      • @shadow 也许我们需要在代码中做更多的检查。如果可以在github上分享代码,也许我可以帮忙看看。
      猜你喜欢
      • 2016-05-26
      • 1970-01-01
      • 2021-11-26
      • 2017-06-05
      • 1970-01-01
      • 1970-01-01
      • 2021-09-27
      • 2023-04-09
      • 2015-12-01
      相关资源
      最近更新 更多