【问题标题】:How do I correctly test asynchronous stream error event handling with mocha/sinon/chai?如何使用 mocha/sinon/chai 正确测试异步流错误事件处理?
【发布时间】:2021-03-14 15:38:37
【问题描述】:

我正在测试一个异步方法,该方法使用 NodeJS 中的本机 https.request() 方法从 Web 请求中返回一些数据。我正在使用 mocha、chai 和 sinon 以及它们各自的相关扩展。

我正在测试的方法基本上将 NodeJS 文档中提供的样板 https.request() 代码包装在 Promise 中,并在收到响应“结束”事件时解析,或者如果收到请求“错误”事件则拒绝。与讨论相关的部分如下所示:

async fetch(...) {
    // setup

    return new Promise((resolve, reject) => {
        const req = https.request(url, opts, (res) => {
            // handle response events
        });
        
        req.on('error', (e) => {
            logger.error(e);  // <-- this is what i want to verify
            reject(e);
        });

        req.end();
    });
}

如评论中所示,我要测试的是,如果发出请求错误事件,则错误会被正确记录。这就是我目前正在做的事情:

it('should log request error events', async () => {
    const sut = new MyService();
    const err = new Error('boom');
    const req = new EventEmitter();
    req.end = sinon.fake();
    const res = new EventEmitter();
    sinon.stub(logger, 'error');
    sinon.stub(https, 'request').callsFake((url, opt, cb) => {
        cb(res);
        return req;
    });

    try {
        const response = sut.fetch(...);
        req.emit('error', err);
        await response;
    } catch() {}

    logger.error.should.have.been.calledOnceWith(err);
});

这感觉像是一个 hack,但我不知道如何使用正常模式正确执行此操作。主要问题是我需要在调用方法之后但在履行承诺之前发出错误事件,如果我像通常使用 mocha 一样返回承诺,我看不到该怎么做。

【问题讨论】:

  • 我看不出有什么问题。您可能还想在request 假冒中放入setTimeout(() =&gt; req.emit('error', err), demoDelay);,然后写await sut.fetch(…);,但任何一种方式都可以。
  • 当你说“如果我像平常对待 mocha 那样返回承诺,我看不出该怎么做。”时你指的是什么?你的 async 函数确实返回了一个对 mocha 的承诺。
  • @Bergi 你的 setTimeout 想法给了我想要的答案,谢谢!

标签: javascript node.js asynchronous mocha.js


【解决方案1】:

我应该想到这一点,但@Bergi 关于在假货中使用 setTimeout() 的评论给了我一个想法,我现在可以使用首选语法:

it('should log request error events', () => {
    const sut = new MyService();
    const err = new Error('boom');
    const req = new EventEmitter();
    req.end = sinon.fake();
    const res = new EventEmitter();
    sinon.stub(logger, 'error');
    sinon.stub(https, 'request').callsFake((url, opt, cb) => {
        cb(res);
        setTimeout(() => { req.emit('error', err); });
        return req;
    });

    return sut.fetch(...).should.eventually.be.rejectedWith(err)
        .then(() => {
            logger.error.should.have.been.calledOnceWith(err);
        });
});

除非我专门测试延迟功能,否则我不喜欢在单元测试中添加任何延迟,因此我使用了延迟为 0 的 setTimeout() 来将发出调用推送到消息队列的末尾。通过将其移至假的,我能够使用 Promise 链接来测试对记录器方法的调用。

【讨论】:

  • 您的原始代码也使用了 Promise 链接,唯一的区别是它将 Promise 存储在临时变量 response 中。我认为最好使用async/await 语法进行链接而不是.then() 调用?
  • @Bergi 我还在学习,但我想我明白你在幕后实际发生的事情的观点。在原始代码中,即使 logger check 是一个单独的语句,因为它是一个异步函数,await 之后的语句被链接起来,就像调用 then() 一样。这个很有见地,谢谢!是的,在普通代码中我更喜欢 async/await,但在单元测试的情况下,我试图保持一致的 BDD 风格,所以这个版本读起来更像其他测试,即使几乎没有潜在的差异。
猜你喜欢
  • 2018-09-03
  • 2018-02-13
  • 2017-10-23
  • 1970-01-01
  • 2015-10-23
  • 2021-10-03
  • 2017-10-08
  • 2017-03-25
  • 1970-01-01
相关资源
最近更新 更多