如何断言与 sinon 和 chai 的承诺

how to assert catch promise with sinon and chai

我们的 CLI 中有一个方法,它使用方法返回承诺以向用户打印消息。

exports.handler = (argv) => {
  let customUtils = new Utils(argv);

  Utils.deploy()
    .then(res => console.log(`Ressource was deployed`))
    .catch(e => {
      console.error(`Ressource was not deployed`);
      console.error(e);
      process.exit(1);
    });
}

我们正在寻找一种方法来测试控制台错误和进程退出,以防 deploy() 拒绝承诺。

我们尝试使用沙盒存根然后在异步测试中断言:

describe('when promise is errored', () => {
  beforeEach(() => {
    sandbox = sinon.createSandbox();
    utilsStub = sandbox.stub(Utils.prototype, 'deploy').rejects('rejected');
    processStub = sandbox.stub(process, 'exit');
    consoleStub = sandbox.stub(console, 'error');
  });

  afterEach(() => {
    sandbox.restore();
  });

  it('should call deploy and log the error before exiting', async () => {
    await handler({});

    expect(utilsStub).to.have.been.called;
    expect(console.error).to.have.been.called;
  });
});

此测试无效:AssertionError: expected error to have been called at least once, but it was never called

当我们 expect(process.exit).to.have.been.called; 时也会发生同样的情况。它从未被调用过。

我们以类似的方式成功测试了 then 部分:

describe('when promise is resolved', () => {
  beforeEach(() => {
    sandbox = sinon.createSandbox();
    utilsStub = sandbox.stub(Utils.prototype, 'deploy').callsFake(() => Promise.resolve('some text'));
    consoleStub = sandbox.stub(console, 'log');
  });

  afterEach(() => {
    sandbox.restore();
  });

  it('should call deploy and print success message', async () => {
    await handler({});

    expect(utilsStub).to.have.been.called;
    expect(console.log).to.have.been.calledWith('Ressource was deployed');
  });
});

有些东西需要修复源文件和测试文件。

对于源文件,我们必须使用customUtils调用deploy()函数。因为,你可以使用 async/await,从 Promise 转换它可以产生更好的代码。

exports.handler = async argv => { // put async
  let customUtils = new Utils(argv);
  try {
    await customUtils.deploy(); // change to await and use customUtils
    console.log(`Ressource was deployed`);
  } catch (e) {
    console.error(`Ressource was not deployed`);
    console.error(e); 
    process.exit(1);
  }
};

对于测试文件,没有任何变化

describe('when promise is errored', () => {
  beforeEach(() => {
    sandbox = sinon.createSandbox();
    utilsStub = sandbox.stub(Utils.prototype, 'deploy').rejects('rejected');
    processStub = sandbox.stub(process, 'exit');
    consoleStub = sandbox.stub(console, 'error');
  });

  afterEach(() => {
    sandbox.restore();
  });

  it('should call deploy and log the error before exiting', async () => {
    await handler({});

    expect(utilsStub).to.have.been.called;
    expect(console.error).to.have.been.called;
    expect(process.exit).to.have.been.called; // add it
  });
});

更新:

如果仍想使用 promise,我们必须确保 return promise。

exports.handler = (argv) => {
  let customUtils = new Utils(argv);

  return customUtils.deploy() // <== specify return here
    .then(res => console.log(`Ressource was deployed`))
    .catch(e => {
      console.error(`Ressource was not deployed`);
      console.error(e);
      process.exit(1);
    });
};

希望对您有所帮助

在测试您的断言之前,您需要能够 await exports.handler 的结果。你 正在 等待它,但是 exports.handler 没有返回承诺,所以在测试中没有什么等待 — exports.handler returns 立即未定义所以在可以调用 console.error 之前,测试在同一事件循环中运行断言。

我不确定为什么您在 promise 解决的测试中没有看到类似的问题。 (也许值得检查该测试是否正确失败)

这应该有帮助:

exports.handler = (argv) => {
  let customUtils = new Utils(argv);

  //Utils.deploy() // <- is that a typo?

    return customUtils.deploy()
      .then(res => console.log(`Ressource was deployed`))
      .catch(e => {
          console.error(`Ressource was not deployed`);
          console.error(e);
          process.exit(1);
       });
}

同样在你的测试中,你正在创建一个间谍:

consoleStub = sandbox.stub(console, 'error');

而是直接在console.error上写断言。我不认为这应该有效:

expect(console.error).to.have.been.called;
// maybe expect(consoleStub)...

通过这些更改,我的测试通过了,但(更重要的是)当我没有在 catch 中调用 console.error 时,测试失败了。