断言 forEach 循环内的 catch 块代码

Asserting on catch block code inside forEach loop

我很难编写测试来断言在 forEach 循环内执行的 catch 块内发生的事情。

产品代码

function doSomething(givenResourceMap) {
  givenResourceMap.forEach(async (resourceUrl) => {
    try {
        await axios.delete(resourceUrl);
    } catch (error) {
        logger.error(`Error on deleting resource ${resourceUrl}`);
        logger.error(error);
        throw error;
    }
});

我想断言 logger.error 被调用了两次并且每次都使用正确的参数调用。所以我写了一些这样的测试

describe('Do Something', () => {
  it('should log message if fail to delete the resource', function() {        
    const resource1Url = chance.url();
    const givenResourceMap = new Map();
    const thrownError = new Error('Failed to delete');
    givenResourceMap.set(resource1Url);

    sinon.stub(logger, 'error');
    sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);

    await doSomething(givenResourceMap);

    expect(logger.error).to.have.callCount(2);
    expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
    expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
    // Also need to know how to assert about `throw error;` line
  });
});

我正在使用 Mocha、sinon-chai,期待测试。上面的测试失败说 logger.error 是 0 次。

谢谢。

问题是您在 return 没有 Promise 的函数上使用 await。请注意,doSomething 不是 async,也不是 return 一个 Promise 对象。

forEach 函数是 async 但这意味着他们会 return 立即解决 Promise 而你永远不会 await在他们身上。

实际上,doSomething 会在 forEach 内部的工作完成之前 return,这可能不是您想要的。为此,您可以使用像这样的常规 for 循环:

async function doSomething(givenResourceMap) {
  for (const resourceUrl of givenResourceMap) {
    try {
        await axios.delete(resourceUrl);
    } catch (error) {
        logger.error(`Error on deleting resource ${resourceUrl}`);
        logger.error(error);
        throw error;
    }
  }
}

请注意,它将 doSomething 的 return 类型更改为 Promise 对象,而不是像最初那样 returning undefined。但它确实允许您在测试中按照您的意愿对其执行 await(并且可能也在生产代码中)。

但是由于您重新抛出循环中捕获的异常,您的测试将异常退出。测试代码也必须更改以捕获预期的错误:

it('should log message if fail to delete the resource', function(done) { 
  // ... the setup stuff you had before...       
  await doSomething(givenResourceMap).catch(err => {
    expect(logger.error).to.have.callCount(2);
    expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
    expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
    done();
  });
});