Sinon 忽略后续的异步调用

Sinon ignores subsequent async call

我需要验证所有交易调用是否成功。 这是示例:

module.js

const functionUnderTest = async (helper) => {
  await helper.transaction(async (transaction) => {
    await helper.doSomething();
    await helper.doSomething2();
    await helper.expectedToBeCalled();
    console.log("done");
  });
};
module.exports = {
  functionUnderTest,
};

问题来了,出于某种原因,对于 sinon,我无法验证最后一次调用是否已完成,但始终打印消息“完成”。

这是输出的样子


$ npm run test

> sinon-test@1.0.0 test
> mocha



  module
    ✔ the former function should be called
done
    1) the latter function should be called
done


  1 passing (7ms)
  1 failing

  1) module
       the latter function should be called:
     AssertionError: expected stub to have been called at least once, but it was never called
      at Context.<anonymous> (test/module.test.js:31:48)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

测试本身如下所示

helpers/chai.js

const chaiAsPromised = require("chai-as-promised");
const chaiSinon = require("sinon-chai");

chai.use(chaiSinon);
chai.use(chaiAsPromised);

module.exports = { expect: chai.expect };

test/module.test.js

const { expect } = require("./helpers/chai");
const sinon = require("sinon");
const { functionUnderTest } = require("../module");

describe("module", function () {
  let sandbox;
  let mockHelper;
  const transaction = {};

  beforeEach(function () {
    sandbox = sinon.createSandbox();
    mockHelper = {
      transaction: sandbox.stub().callsArgWithAsync(0, transaction),
      doSomething: sandbox.stub().resolves({}),
      doSomething2: sandbox.stub().resolves({}),
      expectedToBeCalled: sandbox.stub().resolves({}),
    };
  });

  afterEach(function () {
    sandbox.restore();
  });

  it("the former function should be called", async function () {
    await functionUnderTest(mockHelper);
    expect(mockHelper.doSomething).to.be.called;
  });

  it("the latter function should be called", async function () {
    await functionUnderTest(mockHelper);
    expect(mockHelper.expectedToBeCalled).to.be.called;
  });
});

为了使其完整且易于测试,我创建了一个您可以使用的存储库:https://github.com/vichugunov/sinon-test

我的问题是:

P.S。如果调用 await helper.doSomething2() 被注释掉,则测试通过

您误解了存根的异步版本 callsArg*yields*。看到这个 PR,区别在于异步版本 callsArg* 将:

  • In Node environment the callback is deferred with process.nextTick.
  • In a browser the callback is deferred with setTimeout(callback, 0).

等待异步回调函数完成。 async/await在测试用例中只能等待await helper.transaction()异步函数完成,不能等待其异步回调

callsArg*的同步版本会立即调用回调。你可以看看这个issue,什么时候应该使用异步版本callsArg*

因此,当测试用例执行断言时,helper.expectedTobecalled()等异步函数没有完成

您应该使用 callsFake()helper.transaction() 方法提供异步存根回调并使用 async/await.

调用它

例如

module.js:

const functionUnderTest = async (helper) => {
  await helper.transaction(async (transaction) => {
    await helper.doSomething();
    await helper.doSomething2();
    await helper.expectedToBeCalled();
    console.log('done');
  });
};
module.exports = { functionUnderTest };

module.test.js:

const sinon = require('sinon');
const { functionUnderTest } = require('./module');

describe('module', function () {
  let sandbox;
  let mockHelper;
  const transaction = {};

  beforeEach(function () {
    sandbox = sinon.createSandbox();
    mockHelper = {
      transaction: sandbox.stub().callsFake(async (callback) => {
        await callback(transaction);
      }),
      doSomething: sandbox.stub().resolves({}),
      doSomething2: sandbox.stub().resolves({}),
      expectedToBeCalled: sandbox.stub().resolves({}),
    };
  });

  afterEach(function () {
    sandbox.restore();
  });

  it('the former function should be called', async function () {
    await functionUnderTest(mockHelper);
    sinon.assert.calledOnce(mockHelper.doSomething);
  });

  it('the latter function should be called', async function () {
    await functionUnderTest(mockHelper);
    sinon.assert.calledOnce(mockHelper.expectedToBeCalled);
  });
});

测试结果:

  module
done
    ✓ the former function should be called
done
    ✓ the latter function should be called


  2 passing (5ms)