UNPKG

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