1

I want to take advantage of Mocha's built in promise support, but i'm having difficulty dealing with false positives when I want to test catch branches in my promise chains.

This question gets closest to what I want, but the solution requires every developer on my project to add a then that will throw an error, and ensure that that error doesn't accidentally pass the test.

Because of this, i've had to revert to using the done style of tests, which relies on the built in timeout to catch errors instead of an assertion. This is less than ideal, but removes the chance of false positives.

var RSVP = require('RSVP');

function request (shouldSucceed) {
  if (shouldSucceed) {
    return RSVP.Promise.resolve('success');
  } else {
    return RSVP.Promise.reject(new Error('failure'));
  }
}

describe('request', function () {
  it('throws an error if shouldSucceed is not provided', function () {
    // this test is incorrectly calling `request`
    // which leads to it passing without actually testing
    // the behaviour it wants to
    return request(true)
      .catch(function (err) {
        expect(err).to.be.an.instanceof(Error)
      });
  });

  it('throws an error if shouldSucced is not provided (manual then block)', function () {
    // this test tacks a `then` onto the chain, to ensure `request`
    // actually throws an error as expected
    // unfortunately, it still passes since the assertion needs to do
    // a little extra work to ensure the type of error thrown is the
    // expected error
    return request(true)
      .then(function () {
        throw new Error('Not expected');
      })
      .catch(function (err) {
        expect(err).to.be.an.instanceof(Error)
      });
  });

  it('throws an error if shouldSucceed is not provided (done syntax)', function (done) {
    // this assertion fails (as it should)
    // due to a timeout
    return request(true)
      .catch(function () {
         expect(err).to.be.an.instanceof(Error);
         done()
      });
  });
});

Output:

  request
    ✓ throws an error if shouldSucceed is not provided
    ✓ throws an error if shouldSucced is not provided (manual then block)
    1) throws an error if shouldSucceed is not provided (done syntax)


  2 passing (2s)
  1 failing

  1) request throws an error if shouldSucceed is not provided (done syntax):
     Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

Is there a cleaner way to tell mocha that I am expecting something to happen in a catch block, and that a successful resolution of the promise should be a test failure?

Community
  • 1
  • 1
Nick Tomlin
  • 28,402
  • 11
  • 61
  • 90
  • I don't get what you want. What is the syntax you'd like to use, and what "false positives" would you like to detect? – Bergi Jun 06 '15 at 19:31
  • @Bergi I've updated the question to (hopefully) be clearer. Essentially I want to prevent the situation where a test is passing for the wrong reasons, e.g. i've created a promise chain that resolves in a way that bypasses my assertion. – Nick Tomlin Jun 06 '15 at 19:57

1 Answers1

1

You're looking for

it('request(false) should throw an error', function () {
    return request(false).then(function() {
        throw new Error('unexpected success');
    }, function(err) {
        expect(err).to.be.an.instanceof(Error)
    });
});

See When is .then(success, fail) considered an antipattern for promises? for an explanation of the difference to the code that you currently have.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks! This makes a lot of sense. It's unfortunate that there's always a requirement to manually handle cases, but I don't think there is a way around it :| I've accidentally forgotten to guard against a success and spent needless time debugging. – Nick Tomlin Jun 06 '15 at 20:50
  • Well, you can always write a helper function like `expectToBeRejected` or so that wraps this, if you find yourself doing this often in tests. – Bergi Jun 06 '15 at 21:27