【问题标题】:Mocha Chai Sinon test inaccessible promise / async / event-emitterMocha Chai Sinon 测试无法访问的 promise / async / event-emitter
【发布时间】:2017-09-14 07:58:32
【问题描述】:

我的设置使用chaisinonchai-sinonchai-as-promisedbabel 和 es6 语法。

我有以下(简化的)代码

// example.js
'use strict';
import EventEmitter from 'events';

class Dispatcher extends EventEmitter {
  send(x) {
    doSomethingAsync() // promise is NOT returned 
      .then(() => {
        this.emit('sent');
      })
      .catch((err) => {
        this.emit('error', err);
      });
  }
}

注意:doSomethingAsync 的承诺不会被返回。 (永远不会)

这是我的(简化的)测试文件

let dispatcher;
let onSent;
let onError;

beforeEach(() => {
  dispatcher = new Dispatcher();

  onSent = sinon.stub();
  onError= sinon.stub();
  dispatcher.on('sent', onSent);
  dispatcher.on('error', onError);
});

describe('send', () => {
  it('should emit "error" on sendFn error instead of "sent"', () => {
    ... set up state for failure ...
    dispatcher.send(...);
    ... What do I do here or how do I wrap the following? ...
    expect(onSent).not.to.have.been.called;
    expect(onError).to.have.been.called;
  });
});

如果我可以将doSomethingAsync 的承诺作为send 的结果返回,我知道该怎么做,但这里不是这样。我所拥有的只是知道最终会发出“发送”或“错误”事件。

我理想的语法应该是这样的:

expect(onError).to.eventually.have

但是,这行不通。只需将expect 包装在一个新的承诺中,我就可以获得一个无错误的版本,如下所示。但我不知道为什么会这样。

  // This one works for some unknown reason!
  it('should emit "send" on send success', () => {
    ... set up state for success ...
    dispatcher.send(...);
    return Promise.resolve().then(() => {
       expect(onSent).to.have.been.called;
       expect(onError).not.to.have.been.called;
    });     
  });

如果我可以重构代码以暴露内部承诺,那么这将是微不足道的解决方案。在其他情况下,我已经无数次这样做了。然而,我的问题是非常具体如何解决这个 exact 模式;即如何测试无法访问的承诺或异步代码的副作用,特别是当我无法重构代码以公开承诺时,在事件发射器周围。

我至少尝试了以下方法,看看是否可以在测试调用期望之前触发所需的发送函数以完成其所有内部回调/承诺

  • 将期望包装在一个承诺中
  • 使用sinon.useFakeTimers控制时间
  • 在超时中包装期望
  • 以各种异步/等待模式包装发送调用和期望

提前致谢。

编辑:

好的,这完全是荒谬的,但这里有一个适用于已解决和已拒绝的承诺的解决方案:

it('should behave as expected already!', (done) => {
  ... set up for failure or success as desired
  dispatcher.send();
  process.nextTick(() => {
    Promise.resolve().then(() => {
      ... expectations ...
      done();
    });
  });
});

我认为这是可行的,因为(我完全在猜测!)我假设抛出的错误或被拒绝的承诺会在当前滴答中立即处理,而已解决的承诺会在下一个滴答中排队。所以...process.nextTick 确保我们将在下一个滴答声中排队这个函数,允许所有捕获/错误完成,并且 Promise.resolve 确保它在任何已经排队的承诺运行后排队。顺便说一句,您还可以切换 order 或 nextTick 和 promise.resolve() ,它工作得很好。

NB 如果事件是真正异步发出的(例如在它们自己的process.nextTick 中,那么您必须有一个 3 级嵌套。Promise-nextTick-promise 或 nextTick-promise-nextTick。

我的话是乱七八糟的!

...虽然仍然比超时更好:D

【问题讨论】:

    标签: node.js testing promise sinon chai


    【解决方案1】:

    由于测试不会等待您正在测试的代码块来解决/拒绝承诺,因此断言将不起作用。

    您的分析都是正确的,但解决的方法是解决断言中的承诺。

    我对您的代码进行了一些更改并使其正常工作。

    我已经让doSomethingAsync 兑现承诺(我认为这是正确的解决方案)

    //要测试的代码

    import EventEmitter from 'events';
    
    const doSomethingAsync = async (x) => {
      return x;
    };
    
    class Dispatcher extends EventEmitter {
      send(x) {
        return doSomethingAsync(x)
          .then((res) => {
             this.emit('sent');
          })
          .catch((err) => {
             this.emit('error', err);
          });
      }
    }
    
    export default Dispatcher;
    

    // 测试

    选项 1:使用等待

    import sinon from 'sinon';
    import { expect } from 'chai';
    import Dispatcher from '../dispatcher';
    
    describe('send', () => {
      let dispatcher;
      let onSent;
      let onError;
    
      beforeEach(() => {
        dispatcher = new Dispatcher();
        onSent = sinon.stub();
        onError = sinon.stub();
        dispatcher.on('sent', onSent);
        dispatcher.on('error', onError);
      });
    
      it('Test 1: should emit "error" on sendFn error instead of "sent"', async () => {
        await dispatcher.send('hello');
        expect(onSent.callCount).to.equal(1);
        expect(onError.callCount).to.equal(0);
      });
    });
    

    // 结果

    纱线运行规范

    发送 ✓ 应该在 sendFn 错误时发出“error”而不是“sent”

    1 次通过 (1s)

    ✨ 4.18 秒内完成。

    选项 2:使用承诺

    在测试中,你可以看到我在 dispatcher.send 上使用了 await 或者你也可以使用如下的 Promise 断言

    it('Test 2: should emit "error" on sendFn error instead of "sent"', () => {
      let error = false;
      dispatcher.send('hello').then(() => {
        expect(onSent.callCount).to.equal(1);
        expect(onError.callCount).to.equal(0);
      }).catch(() => {
        error = true;
      })
      expect(error).to.equal(false);
    });
    

    PS:- 如果你不想返回 Promise,那么第一个测试用例(使用 await 的那个)将通过,第二个测试用例将失败。

    【讨论】:

      【解决方案2】:

      基于 anoop 给出的答案,您可以执行以下操作:

      function sinonSetUp( eventEmitter )
      {
          let onSent = sinon.stub();
          let onError = sinon.stub();
          eventEmitter.on( 'sent', onSent );
          eventEmitter.on( 'error', onError );
          return ({
              onSent: onSent,
              onError: onError,
          });
      }
      
      it('emits a sent event', async function()
      {
          let dispatcher = new Dispatcher();
          let sinonHandlers = sinonSetUp(dispatcher);
          let promise = dispatcher.send();
          await promise;
      
          //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
      
          //Checking the error event:
          //The way you wish you could do it:
          /* 
          dispatcher.on('error', function(errEventVal)
          {
              expect(errEventVal).to.be.undefined; 
          }); 
          */
      
          //The way you have to do it:
          //args[0] gets the returned arguments, if any exist
          let errEventArgs = sinonHandlers.onError.args[0]; 
          expect(errEventArgs).to.be.undefined;
      
          //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
      
          //Checking the sent event:
          //The way you wish you could do it:
          /* 
          dispatcher.on('sent', function(sentEventVal)
          { 
              expect(sentEventVal).not.to.be.undefined; 
          }); 
          */
      
          //The way you have to do it:
          //If args[0] exists, then the value will be in args[0][0]
          //See the documentation for sinonjs.
          let sentEventArgs = sinonHandlers.onSent.args[0];
          expect(sentEventArgs).not.to.be.undefined;
          let sentEventVal = sentEventArgs[0];
          expect(sentEventVal).not.to.be.undefined;
      });
      

      【讨论】:

        猜你喜欢
        • 2015-12-01
        • 1970-01-01
        • 2023-04-11
        • 2018-04-08
        • 2016-06-11
        • 2016-05-26
        • 2018-11-05
        • 2019-02-04
        • 1970-01-01
        相关资源
        最近更新 更多