Giter Club home page Giter Club logo

chai-events's Introduction

Chai Events

BrowserStack Status

Make assertions about event emitters.

const chai = require("chai");
chai.use(require("chai-events"));
const should = chai.should();
const EventEmitter = require("events");

describe("Event Emitting", function() {

  let emitter = null;
  beforeEach(function() {
    emitter = new EventEmitter();
  });

  it("should get emitted events", function() {
    let p = emitter.should.emit("get");
    setTimeout(function() {
      emitter.emit("get");
    }, 200);
    return p;
  });

  it("should handle non-emitted events", function() {
    emitter.should.not.emit("missing");
  });

  it("can be configured", function() {
    emitter.should.not.emit("another missing", {
      timeout: 50, // in milliseconds
    });
  });

});

Browser Usage

Browser testing powered by BrowserStack
BrowserStack

chai-events's People

Watchers

 avatar  avatar

Forkers

vpulim

chai-events's Issues

Feature request: change assertion target to handler argument

Many event emitters attach data when emitting events. It would be nice if we could test assertions on that data:

return expect(emitter).to.emit('event').that.equals(42);

At the moment, this is rather difficult, like awaiting a Promise that resolves when the event I am looking for is emitted:

return expect(new Promise(resolve => emitter.on('event', (...args) => resolve(args)))).to
    .eventually.equal(/* expected arguments */); // Using chai-as-promised

I think the required changes are probably not very big. Mostly emit would have to be marked as chainable, and the assertion target would need to be changed to emitted event's data.

If you're interested in this at all, drop me a reply, and I could have a look at a pull request.

Add configuration for timeout

Currently timeout is defaulting to 1500ms (appropriate for Mocha). Allow user to configure:

(x).should.after(200).not.emit("test");

TypeScript definition

At the moment yout cannot use this in TypeScript because there aren't any TypeScript definition files.

I'm not sure how to create them for an Node library

'chai-as-promised' incompatible with 'chai-events'

NOTE: chai-as-promised has an one significant side affect:

chai.use(require('chai-events'));
chai.use(require('chai-as-promised'));

// 'chai-as-promised' ignores our Promise and returns plain Assertion (or Proxy) object!
// chai.config.useProxy = true(default)/false;
const onEvent = expect(eventEmiter).to.emit('eventA'); // <- lost the Promise !
await onEvent; // <- doesn't wait 

It happen only if expection chain doesn't include eventually property. Underhood plugin wraps all chai methods and add then function with custom logic only if this._obj is promise. But in our case the tested object is EventEmiter. Anyway if try to add eventually to example above, we catch an error: eventNames: [Function: eventNames] } is not a thenable.

Workaround: put chai-event after chai-as-promised

chai.use(require('chai-as-promised'));
chai.use(require('chai-events'));

Possible fix:

Assertion.addMethod('emit', function(name, timeout) {
    const promise = new Promise((resolve, reject) => {...};

    // If use 'chai-as-promised' after 'chai-events'
    // make Assertion then-able to allow 'await' 
    this.then = promise.then.bind(promise);

    // if use 'chai-as-promised' before 'chai-events' or without 'chai-as-promised'
    // return Promise as usual
    return promise;
});

Propousal:
It would be nice to mention about such issue in README.md

Negative flag doesn't work as expected

Test should FAIL but it's OK (chai v4.1.2):

describe('chai-events', function() {
    const eventEmitter = new EventEmitter();

    it('should not emit', async function() {
        const onEventA = expect(eventEmitter).to.have.not.been.emit('eventA');
        eventEmitter.emit('eventA');
        await onEventA;
    });
});

Root cause:
https://github.com/CodeLenny/chai-events/blob/master/chai-events.js#L71

if(utils.flag(this, 'negate')) {
...
    assert(false, "expected #{this} to not emit "+name.toString()+".");
...
}

If you look at chai.Assertion.assert() API you can see that util.test(this, arguments) converts expr from false to true if negative flag is turned ON

https://github.com/chaijs/chai/blob/master/chai.js#L231

Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) {
  var ok = util.test(this, arguments);
  ...
  if (!ok) {
    ...
    throw new AssertionError(...)
  };

https://github.com/chaijs/chai/blob/master/lib/chai/utils/test.js#L27

module.exports = function test(obj, args) {
  var negate = flag(obj, 'negate')
    , expr = args[0];
  return negate ? !expr : expr;
};

Fix: Change wrong assert() usage to:

assert(true, '', 'expected #{this} to not emit '+name.toString() + '.');

Allow the user to configure emitter checking

Currently, x.should.be.an.emitter allows:

  • x instanceof EventEmitter
  • x instanceof EventTarget
  • typeof x[method] === "function" for method in ["on", "emit", "off"]
  • typeof x[method] === "function" for method in ["addEventListener", "dispatchEvent", "removeEventListener"]

Allow the user to specify which of the above rules are allowed.

(x).should.be.a.custom.emitter // Either browser or node
(x).should.be.a.browser.emitter
(x).should.be.a.node.emitter

(or something along those lines)

UnhandledPromiseRejectionWarning

Wrong Promise rejection implementation:

assert() just throws exception and DOESN'T reject Promise that we expect in out test.
In this case sometimes we see UnhandledPromiseRejectionWarning.

Case 1

  const eventEmitter = new EventEmitter();
  const onEventA = expect(eventEmitter).to.not.emit('eventA');
  eventEmitter.emit('eventA');  // <--- HERE we get exception from on() handler
  await onEventA;               //  NOT HERE !
});

Case 2

  const eventEmitter = new EventEmitter();
  const onEventA = expect(eventEmitter).to.emit('eventA');

  await onEventA;                 // Promise will be NEVER resolved
  // Test fails only because setTimeout throws an error and mocha handles it
});

Tech details

Possible solution
Use try-catch for setTimeout() and on() handlers:

try {
    assert(...);
    resolve(args);
} catch (err) {
    reject(err);
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.