Announcing: Chai as Promised

523 views
Skip to first unread message

Domenic Denicola

unread,
Mar 29, 2012, 3:15:57 PM3/29/12
to cha...@googlegroups.com

Stepan Riha

unread,
Mar 29, 2012, 5:11:00 PM3/29/12
to cha...@googlegroups.com
This looks awesome, but can we also get the assert equivalents for this? We prefer using the assert.* syntax because it better matches our non-JS unit tests.

I can envision the following:

  1. assert.rejects(promise, message);
  2. assert.rejects(promise, /pattern/, message); 
  3. assert.rejects(promise, TypeError, message);

  4. assert.resolves(promise, message);
  5. assert.resolves(promise, function validator(), massage);

That the message is what's to be displayed if the assertion fails. You may also want to have breaks and fulfills as aliases for rejects and resolves.
The validator() function should be passed the arguments that the promise resolved with and it should return true if the resolution values are ok.

The resolves/rejects assert should be returning another promise (which resolves/rejects based on whether the assertions succeeded) s.t. I can easily chain the assertions.

- Stepan

Jake L.

unread,
Mar 30, 2012, 3:14:40 AM3/30/12
to cha...@googlegroups.com
Amazing work Domenic! Looking forward to trying this out.

Domenic Denicola

unread,
Apr 1, 2012, 7:02:47 PM4/1/12
to cha...@googlegroups.com

For sure; I can work on assert over the upcoming week.

 

Recall that, while broken and rejected are synonyms, there’s a difference between “resolved” and “fulfilled.” There are two dichotomies: pending vs. resolved, and fulfilled versus rejected. Thus, a rejected promise is still resolved.

 

My full vision:

 

assert.isRejected(promise, message);

assert.isRejected(promise, /pattern/, message);

assert.isRejected(promise, TypeError, message);

// + assert.isBroken synonyms

 

assert.isFulfilled(promise, message);

 

assert.eventually.isNumber(promise, message);

assert.eventually.typeOf(promise, "string", message);

// etc.

 

And as you say, they will return promises so that you can chain.

Domenic Denicola

unread,
Apr 1, 2012, 7:40:25 PM4/1/12
to cha...@googlegroups.com
I am considering the following breaking change. Please let me know what you think. TL;DR version:

promiseFor(2 + 2).should.eventually.be.ok.then(done, done)

would no longer work, to be replaced by

promiseFor(2 + 2).should.eventually.be.ok.callback(done)

== The problem ==

When building 1.0, I was concerned with supporting the `.then(done, done)` style for frameworks like Mocha, wherein you are given a single `done` callback to signal the end of an async test. If you pass it nothing, it assumes the test passed; otherwise, the test fails. The idea is that `done` is somewhat like a Node.js callback.

Thus, when running the following code:

promiseFor(2 + 2).should.eventually.be.ok.then(done, done);

while `be` returns an assertion-promise [1] for 42, `ok` actually returns a promise for `undefined`, since otherwise `done` would be called with 42 and assume an error occurred.

This forces an unfortunate situation. The most visible consequence is that you cannot chain assertions. That is,

promiseFor(2 + 2).should.eventually.be.ok.and.equal(4).then(done, done);

does not work, since `ok` ends the chaining and must return a promise fulfilled with `undefined`, so by the time `equal` gets called, it doesn’t have anything to test `4` against.

The other problem is that the way in which this is coded [2] is rather fragile, as I need to manually categorize asserters into "end of chain" or "still chaining." Currently this is done by saying that all function asserters (e.g. `equal`, `include`, `keys`) are "end of chain," as well as a small whitelist of property-asserters (viz. `arguments`, `empty`, `exist`, `false`, `ok`, and `true`). All others are assumed to be "still chaining." This is not only future-unfriendly, it is also plugin-unfriendly.

[1]: that is, a promise that has all of the asserters, like `ok`, `equal`, etc., tacked onto it
[2]: https://github.com/domenic/chai-as-promised/blob/71e23309cae82b367c86a2eb29725122ba5323fc/lib/chai-as-promised.js#L24-28

== The proposed solution ==

Chai as Promised would no longer take pains to fulfill assertion-promises with `undefined`. That is,

promiseFor(2 + 2).should.eventually.be.ok.then(done, done);

would now throw up, since `4` would be passed to `done`. The naïve solution is then

promiseFor(2 + 2).should.eventually.be.ok.then(function () { done(); }, done);

But of course I would then just augment assertion-promises with a new capstone method to do this for you, e.g.

promiseFor(2 + 2).should.eventually.be.ok.end(done) // kind of conflicts with Q
promiseFor(2 + 2).should.eventually.be.ok.callback(done) // probably my favorite?
promiseFor(2 + 2).should.eventually.be.ok.cb(done)
promiseFor(2 + 2).should.eventually.be.ok.makeSureToInform(done) // mostly kidding
promiseFor(2 + 2).should.eventually.be.ok.whyDoesntHeJustAcceptMochaPullRequestNumber329(done) // definitely kidding [3]

What do you think?

[3]: https://github.com/visionmedia/mocha/pull/329
Message has been deleted

Jake L.

unread,
Apr 2, 2012, 2:25:07 AM4/2/12
to cha...@googlegroups.com
I definitely see the issue here. 

My preference is `end`, however I do not use Q so I am not sure of what kind of conflict this would cause. If that is an issue than make both `callback` with an alias to `cb` available for use. 

Stepan Riha

unread,
Apr 2, 2012, 8:27:37 AM4/2/12
to cha...@googlegroups.com
Good point.  I was looking at it from the emitters point of view and both jQuery's and CommonJS/A implementation (when.js, node-promise) use the resolve() method to fulfill a promise.

So, yeah, isFulfilled() is unambiguously successful.

FWIW, I would not add an "isResolved" assertion to indicate a promise is no longer in the unresolved state (I, and other looking at my test code, would probably expect assert.isResolved() to fail on a rejected promise).  I might be better to have another, unrelated, term dealing with the transition from the unresolved state (e.g. isAnswered or some such).

Stepan Riha

unread,
Apr 2, 2012, 8:36:59 AM4/2/12
to cha...@googlegroups.com
We don't use fluid assertions (didn't come to unit tests from Ruby, so it still feels forced to me and I don't like the fact that should fails on undefined values).

Having said that, could you use notify?  As in:

promiseFor(2 + 2).should.eventually.be.ok.and.notify(done) 

- Stepan

BTW, my personal preference would most definitely be whyDoesntHeJustAcceptMochaPullRequestNumber329(done). :-)

Jake L.

unread,
Apr 5, 2012, 4:17:46 PM4/5/12
to cha...@googlegroups.com
@Stepan - Just a reminder there is also should.exist(somevar) to ensure prevent non-assertion errors when you run into issue where something might not be defined. I just realized those are not yet properly documented. Added issue https://github.com/logicalparadox/chai/issues/44.

Domenic Denicola

unread,
Apr 9, 2012, 4:02:41 PM4/9/12
to cha...@googlegroups.com
OK, 2.0 is released! It has an `assert` interface, as well as the breaking change discussed earlier, with Stephan's `notify` being the winner.

Enjoy!

Jake L.

unread,
Apr 9, 2012, 4:42:06 PM4/9/12
to cha...@googlegroups.com
Very cool!
Reply all
Reply to author
Forward
0 new messages