11

How should I be testing, with mocha and chai, that my promise has failed?

I am confused, because I initially thought I should be using 'mocha-as-promised', but that package is now deprecated (I'm using mocha 2.1.0), with the advice to just use the promise testing that's now built into mocha. see: https://github.com/domenic/mocha-as-promised

Another post recommends doing away with the 'done' argument to the it() callback - not sure I understand why, since my understanding that passing in the 'done' parameter was the way to signal that a test was being tested asynchronously. see: How do I properly test promises with mocha and chai?

Anyway, I've tried to reduce my issue to the below code - please help me modify this so that I can test that my promise indeed fails.

it.only("do something (negative test)", function (done) {

  var Q = require('q');

  function makePromise() {
    var deferred = Q.defer();
    deferred.reject(Error('fail'));
    return deferred.promise;
  };

  makePromise()
  .then(done, done);

});
Community
  • 1
  • 1
RoyM
  • 1,118
  • 1
  • 9
  • 28

3 Answers3

9

Some more digging, and it appears the right way is to add an additional catch block, like so...

it.only("do something (negative test)", function (done) {

  var Q = require('q');

  function makePromise() {
    var deferred = Q.defer();
    deferred.reject(Error('fail'));
    return deferred.promise;
  };

  makePromise()
  .catch(function(e) {
    expect(e.message).to.equal('fail');
  })
  .then(done, done);

});

I'm interested in alternative ideas, or confirmation that this is fine the way it is.. thanks.

UPDATE:

Ben - I now grok what you were saying, esp. after the terse but helpful comment from Benjamin G.

To summarize:

When you pass in a done parameter, the test is expected to trigger it's 'done-ness' by calling the done() function;

When you don't pass in a done parameter, it normally only works for synchronous calls. However, if you return a promise, the mocha framework (mocha >1.18) will catch any failures that normally would have been swallowed (per the promises spec). Here is an updated version:

it.only("standalone neg test for mocha+promises", function () {

  var Q = require('q');

  function makePromise() {
    var deferred = Q.defer();
    deferred.reject(Error('fail'));
    return deferred.promise;
  };

  return makePromise()
  .catch(function(e) {
    expect(e.message).to.equal('fail');
  });

});
RoyM
  • 1,118
  • 1
  • 9
  • 28
  • 2
    You can `return ` the `makePromise()` and remove the `done, done` part. – Benjamin Gruenbaum Feb 24 '15 at 23:35
  • 2
    I would add an extra assertion to make sure that it did not hit the acceptance case. `return makePromise() .then(() => { // Acceptance case, we should not get here. expect().fail('exception did not appear to be thrown'); }, (e) => { // Failure case, we should get here and the message shoudl match expect(e.message).to.equal('fail'); }); ` – gmetzker Jan 09 '17 at 22:14
3

You can return a promise to signal that the test is asynchronous:

function something() {
  return Q.reject(Error('fail'));
}

it('should reject', function() {
  return something().then(function() {
    throw new Error('expected rejection');
  },
  function() {
    return 'passed :]';
  });
});
Ben
  • 10,056
  • 5
  • 41
  • 42
1

chai-as-promised provides a clean testing framework for Promises:

$ npm install chai-as-promised

In your test file:

var chai = require('chai');
var expect = chai.expect;
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);

...

it('resolves as promised', function() {
    return expect(Promise.resolve('woof')).to.eventually.equal('woof');
});

it('rejects as promised', function() {
    return expect(Promise.reject('caw')).to.be.rejectedWith('caw');
});

This feels clean and intuitive. But you can accomplish something similar without chai-as-promised like this:

it('resolved as promised', function() {
    return Promise.resolve("woof")
        .then(function(m) { expect(m).to.equal('woof'); })
        .catch(function(m) { throw new Error('was not supposed to fail'); })
            ;
});

it('rejects as promised', function() {
    return Promise.reject("caw")
        .then(function(m) { throw new Error('was not supposed to succeed'); })
        .catch(function(m) { expect(m).to.equal('caw'); })
            ;
});
fearless_fool
  • 33,645
  • 23
  • 135
  • 217
  • I am trying to use chai-as-promised, my async method returns promise of deferred library (https://www.npmjs.com/package/deferred). When I am running the test I am getting below error. does deferred supported by this mudule ? "TypeError: Cannot read property 'then' of undefined" – Piyush Beli Sep 26 '16 at 11:43
  • @PiyushBeli: make sure the form that "feeds" the `then` returns a promise. For example, make sure it has a return statement in it. – fearless_fool Sep 26 '16 at 15:57