UNPKG

8.52 kBMarkdownView Raw
1<a href="http://promises-aplus.github.com/promises-spec">
2 <img src="http://promises-aplus.github.com/promises-spec/assets/logo-small.png"
3 align="right" valign="top" alt="Promises/A+ logo" />
4</a>
5
6# Chai Assertions for Working with Promises
7
8**Chai as Promised** extends [Chai][chai] with a fluent language for asserting facts about [promises][presentation].
9
10Instead of manually wiring up your expectations to a promise's fulfilled and rejected handlers:
11
12```javascript
13doSomethingAsync().then(
14 function (result) {
15 result.should.equal("foo");
16 done();
17 },
18 function (err) {
19 done(err);
20 }
21);
22```
23
24you can write code that expresses what you really mean:
25
26```javascript
27doSomethingAsync().should.eventually.equal("foo").notify(done);
28```
29
30or if you have a testing framework that follows the [UncommonJS specification][uncommonjs] for handling promises,
31simply
32
33```javascript
34return doSomethingAsync().should.eventually.equal("foo");
35```
36
37## How to Use
38
39### `should`/`expect` Interface
40
41The most powerful extension provided by Chai as Promised is the `eventually` property. With it, you can transform any
42existing Chai assertion into one that acts on a promise:
43
44```javascript
45(2 + 2).should.equal(4);
46
47// becomes
48return promiseFor(2 + 2).should.eventually.equal(4);
49
50
51expect({ foo: "bar" }).to.have.property("foo");
52
53// becomes
54return expect(promiseFor({ foo: "bar" })).to.eventually.have.property("foo");
55```
56
57There are also a few promise-specific extensions, grouped here as synonymic blocks (with the usual `expect`
58equivalents):
59
60```javascript
61return promise.should.be.fulfilled;
62
63return promise.should.eventually.eql("foo");
64return promise.should.become("foo");
65
66return promise.should.be.rejected;
67return promise.should.be.broken;
68
69return promise.should.be.rejected.with(Error);
70return promise.should.be.broken.with(Error);
71
72// Note: other variants of Chai's existing `throw` assertion work too.
73```
74
75### `assert` Interface
76
77As with the `should`/`expect` interface, Chai as Promised provides an `eventually` extender to `chai.assert`, allowing
78any existing Chai assertion to be used on a promise:
79
80```javascript
81assert.equal(2 + 2, 4, "This had better be true");
82
83// becomes
84return assert.eventually.equal(promiseFor(2 + 2), 4, "This had better be true, eventually");
85```
86
87And there are, of course, promise-specific extensions:
88
89```javascript
90return assert.isFulfilled(promise, "optional message");
91
92return assert.eventually.deepEqual(promise, "foo", "optional message");
93return assert.becomes(promise, "foo", "optional message");
94
95return assert.eventually.notDeepEqual(promise, "foo", "optional message");
96return assert.doesNotBecome(promise, "foo", "optional message");
97
98return assert.isRejected(promise, "optional message");
99return assert.isBroken(promise, "optional message");
100
101return assert.isRejected(promise, Error, "optional message");
102return assert.isBroken(promise, Error, "optional message");
103
104return assert.isRejected(promise, /error message matcher/, "optional message");
105return assert.isBroken(promise, /error message matcher/, "optional message");
106```
107
108### Progress Callbacks
109
110Chai as Promised does not have any intrinsic support for testing promise progress callbacks. The properties you would
111want to test are probably much better suited to a library like [Sinon.JS][sinon], perhaps in conjunction with
112[Sinon–Chai][sinon-chai]:
113
114```javascript
115var progressSpy = sinon.spy();
116
117return promise.then(null, null, progressSpy).then(function () {
118 progressSpy.should.have.been.calledWith("33%");
119 progressSpy.should.have.been.calledWith("67%");
120 progressSpy.should.have.been.calledThrice;
121});
122```
123
124### Working with Non-Promise–Friendly Test Runners
125
126As mentioned, many test runners (\*cough\* [Mocha][mocha-makes-me-sad] \*cough\* … but see [Mocha as Promised][]!)
127don't support the nice `return` style shown above. Instead, they take a callback indicating when the asynchronous test
128run is over. Chai as Promised adapts to this situation with the `notify` method, like so:
129
130```javascript
131it("should be fulfilled", function (done) {
132 promise.should.be.fulfilled.and.notify(done);
133});
134
135it("should be rejected", function (done) {
136 otherPromise.should.be.rejected.and.notify(done);
137});
138```
139
140In these examples, if the conditions are not met, the test runner will receive an error of the form `"expected promise
141to be fulfilled but it was rejected with [Error: error message]"`, or `"expected promise to be rejected but it was
142fulfilled."`
143
144There's another form of `notify` which is useful in certain situations, like doing assertions after a promise is
145complete. For example:
146
147```javascript
148it("should change the state", function (done) {
149 otherState.should.equal("before");
150 promise.should.be.fulfilled.then(function () {
151 otherState.should.equal("after");
152 }).should.notify(done);
153});
154```
155
156Notice how `.notify(done)` is hanging directly off of `.should`, instead of appearing after a promise assertion. This
157indicates to Chai as Promised that it should pass fulfillment or rejection directly through to the testing framework.
158Thus, the above code will fail with a Chai as Promised error (`"expected promise to be fulfilled…"`) if `promise` is
159rejected, but will fail with a simple Chai error (`expected "before" to equal "after"`) if `otherState` does not change.
160
161Another example of where this can be useful is when performing assertions on multiple promises:
162
163```javascript
164it("should all be well", function (done) {
165 Q.all([
166 promiseA.should.become("happy"),
167 promiseB.should.eventually.have.property("fun times"),
168 promiseC.should.be.rejected.with(TypeError, "only joyful types are allowed")
169 ]).should.notify(done);
170});
171```
172
173This will pass any failures of the individual promise assertions up to the test framework, instead of wrapping them in
174an `"expected promise to be fulfilled…"` message as would happen if you did
175`Q.all([…]).should.be.fulfilled.and.notify(done)`.
176
177### Compatibility
178
179Chai as Promised is compatible with all promises following the [Promises/A+ specification][spec]. Notably, jQuery's
180so-called “promises” are not up to spec, and Chai as Promised will not work with them. In particular, Chai as Promised
181makes extensive use of the standard [transformation behavior][] of `then`, which jQuery does not support.
182
183## Installation and Setup
184
185### Node
186
187Do an `npm install chai-as-promised` to get up and running. Then:
188
189```javascript
190var chai = require("chai");
191var chaiAsPromised = require("chai-as-promised");
192
193chai.use(chaiAsPromised);
194```
195
196You can of course put this code in a common test fixture file; for an example using [Mocha][mocha], see
197[the Chai as Promised tests themselves][fixturedemo].
198
199### AMD
200
201Chai as Promised supports being used as an [AMD][amd] module, registering itself anonymously (just like Chai). So,
202assuming you have configured your loader to map the Chai and Chai as Promised files to the respective module IDs
203`"chai"` and `"chai-as-promised"`, you can use them as follows:
204
205```javascript
206define(function (require, exports, module) {
207 var chai = require("chai");
208 var chaiAsPromised = require("chai-as-promised");
209
210 chai.use(chaiAsPromised);
211});
212```
213
214### `<script>` tag
215
216If you include Chai as Promised directly with a `<script>` tag, after the one for Chai itself, then it will
217automatically plug in to Chai and be ready for use:
218
219```html
220<script src="chai.js"></script>
221<script src="chai-as-promised.js"></script>
222```
223
224
225[presentation]: http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript
226[chai]: http://chaijs.com/
227[mocha]: http://visionmedia.github.com/mocha/
228[mocha-makes-me-sad]: https://github.com/visionmedia/mocha/pull/329
229[Mocha as Promised]: https://github.com/domenic/mocha-as-promised
230[uncommonjs]: http://kriskowal.github.com/uncommonjs/tests/specification
231[spec]: http://promises-aplus.github.com/promises-spec/
232[transformation behavior]: https://gist.github.com/3889970#that-second-paragraph
233[fixturedemo]: https://github.com/domenic/chai-as-promised/tree/master/test/
234[amd]: https://github.com/amdjs/amdjs-api/wiki/AMD
235[sinon]: http://sinonjs.org/
236[sinon-chai]: https://github.com/domenic/sinon-chai