mardi 18 avril 2017

Mocha Chai Sinon test inaccessible promise / async / event-emitter

My setup uses chai, sinon, chai-sinon, chai-as-promised, babel and es6 syntax.

I have the following (reduced) code

// 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);
      });
  }
}

Note: the promise from doSomethingAsync is NOT returned. (And never will be)

Here's my (reduced) test file

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;
  });
});

I know how to do this if I could return the promise from doSomethingAsync as the result of send, but that's not the case here. All I have is the knowledge that either the 'sent' or 'error' event will be emitted eventually.

My ideal syntax would look like:

expect(onError).to.eventually.have

But, that doesn't work. I CAN get a non-erroring version to work as follows simply by wrapping the expect in a new promise. But I have no idea why this works.

  // 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;
    });     
  });

If I could refactor the code in such a way as to expose the inner promise, then this would be trivial to solve. I have done so countless times in other circumstances. My question here however is very specifically how to solve this exact pattern; i.e. how to test the side-effects of an inaccessible promise or async code, particularly around event emitters when I cannot refactor the code to expose the promise.

I have tried at least the following just to see if I could trigger the desired send function to complete all its inner callbacks/promises before the test calls the expectations * wrapping the expectations in a promise * controlling time using sinon.useFakeTimers * wrapping the expectations in a timeout * wrapping both send call and expectations in various async/await patterns

Thanks in advance! You're really helping me out.

Aucun commentaire:

Enregistrer un commentaire