1 | # Chai HTTP [![Build Status](https://travis-ci.org/chaijs/chai-http.svg?branch=master)](https://travis-ci.org/chaijs/chai-http)
|
2 |
|
3 | > HTTP integration testing with Chai assertions.
|
4 |
|
5 | #### Features
|
6 |
|
7 | - integration test request composition
|
8 | - test http apps or external services
|
9 | - assertions for common http tasks
|
10 | - chai `expect` and `should` interfaces
|
11 |
|
12 | #### Installation
|
13 |
|
14 | This is a addon plugin for the [Chai Assertion Library](http://chaijs.com). Install via [npm](http://npmjs.org).
|
15 |
|
16 | npm install chai-http
|
17 |
|
18 | #### Plugin
|
19 |
|
20 | Use this plugin as you would all other Chai plugins.
|
21 |
|
22 | ```js
|
23 | var chai = require('chai')
|
24 | , chaiHttp = require('chai-http');
|
25 |
|
26 | chai.use(chaiHttp);
|
27 | ```
|
28 |
|
29 | To use Chai HTTP on a web page, just include the [`dist/chai-http.js`](dist/chai-http.js) file:
|
30 |
|
31 | ```html
|
32 | <script src="chai.js"></script>
|
33 | <script src="chai-http.js"></script>
|
34 | <script>
|
35 | chai.use(chaiHttp);
|
36 | </script>
|
37 | ```
|
38 |
|
39 | ## Integration Testing
|
40 |
|
41 | Chai HTTP provides an interface for live integration
|
42 | testing via [superagent](https://github.com/visionmedia/superagent).
|
43 | To do this, you must first
|
44 | construct a request to an application or url.
|
45 |
|
46 | Upon construction you are provided a chainable api that
|
47 | allows you to specify the http VERB request (get, post, etc)
|
48 | that you wish to invoke.
|
49 |
|
50 | #### Application / Server
|
51 |
|
52 | You may use a function (such as an express or connect app)
|
53 | or a node.js http(s) server as the foundation for your request.
|
54 | If the server is not running, chai-http will find a suitable
|
55 | port to listen on for a given test.
|
56 |
|
57 | __Note:__ This feature is only supported on Node.js, not in web browsers.
|
58 |
|
59 | ```js
|
60 | chai.request(app)
|
61 | .get('/')
|
62 | ```
|
63 |
|
64 | When passing an `app` to `request`; it will automatically open the server for
|
65 | incoming requests (by calling `listen()`) and, once a request has been made
|
66 | the server will automatically shut down (by calling `.close()`). If you want to
|
67 | keep the server open, perhaps if you're making multiple requests, you must call
|
68 | `.keepOpen()` after `.request()`, and manually close the server down:
|
69 |
|
70 | ```js
|
71 | var requester = chai.request(app).keepOpen()
|
72 |
|
73 | Promise.all([
|
74 | requester.get('/a'),
|
75 | requester.get('/b'),
|
76 | ])
|
77 | .then(responses => ....)
|
78 | .then(() => requester.close())
|
79 | ```
|
80 |
|
81 |
|
82 | #### URL
|
83 |
|
84 | You may also use a base url as the foundation of your request.
|
85 |
|
86 | ```js
|
87 | chai.request('http://localhost:8080')
|
88 | .get('/')
|
89 | ```
|
90 |
|
91 | #### Setting up requests
|
92 |
|
93 | Once a request is created with a given VERB, it can have headers, form data,
|
94 | json, or even file attachments added to it, all with a simple API:
|
95 |
|
96 | ```js
|
97 | // Send some JSON
|
98 | chai.request(app)
|
99 | .put('/user/me')
|
100 | .set('X-API-Key', 'foobar')
|
101 | .send({ password: '123', confirmPassword: '123' })
|
102 | ```
|
103 |
|
104 | ```js
|
105 | // Send some Form Data
|
106 | chai.request(app)
|
107 | .post('/user/me')
|
108 | .type('form')
|
109 | .send({
|
110 | '_method': 'put',
|
111 | 'password': '123',
|
112 | 'confirmPassword': '123'
|
113 | })
|
114 | ```
|
115 |
|
116 | ```js
|
117 | // Attach a file
|
118 | chai.request(app)
|
119 | .post('/user/avatar')
|
120 | .attach('imageField', fs.readFileSync('avatar.png'), 'avatar.png')
|
121 | ```
|
122 |
|
123 | ```js
|
124 | // Authenticate with Basic authentication
|
125 | chai.request(app)
|
126 | .get('/protected')
|
127 | .auth('user', 'pass')
|
128 | ```
|
129 |
|
130 | ```js
|
131 | // Chain some GET query parameters
|
132 | chai.request(app)
|
133 | .get('/search')
|
134 | .query({name: 'foo', limit: 10}) // /search?name=foo&limit=10
|
135 | ```
|
136 |
|
137 | #### Dealing with the response - traditional
|
138 |
|
139 | In the following examples we use Chai's Expect assertion library:
|
140 |
|
141 | ```js
|
142 | var expect = chai.expect;
|
143 | ```
|
144 |
|
145 | To make the request and assert on its response, the `end` method can be used:
|
146 |
|
147 | ```js
|
148 | chai.request(app)
|
149 | .put('/user/me')
|
150 | .send({ password: '123', confirmPassword: '123' })
|
151 | .end(function (err, res) {
|
152 | expect(err).to.be.null;
|
153 | expect(res).to.have.status(200);
|
154 | });
|
155 | ```
|
156 |
|
157 | ##### Caveat
|
158 |
|
159 | Because the `end` function is passed a callback, assertions are run
|
160 | asynchronously. Therefore, a mechanism must be used to notify the testing
|
161 | framework that the callback has completed. Otherwise, the test will pass before
|
162 | the assertions are checked.
|
163 |
|
164 | For example, in the [Mocha test framework](http://mochajs.org/), this is
|
165 | accomplished using the
|
166 | [`done` callback](https://mochajs.org/#asynchronous-code), which signal that the
|
167 | callback has completed, and the assertions can be verified:
|
168 |
|
169 | ```js
|
170 | it('fails, as expected', function(done) { // <= Pass in done callback
|
171 | chai.request('http://localhost:8080')
|
172 | .get('/')
|
173 | .end(function(err, res) {
|
174 | expect(res).to.have.status(123);
|
175 | done(); // <= Call done to signal callback end
|
176 | });
|
177 | });
|
178 |
|
179 | it('succeeds silently!', function() { // <= No done callback
|
180 | chai.request('http://localhost:8080')
|
181 | .get('/')
|
182 | .end(function(err, res) {
|
183 | expect(res).to.have.status(123); // <= Test completes before this runs
|
184 | });
|
185 | });
|
186 | ```
|
187 |
|
188 | When `done` is passed in, Mocha will wait until the call to `done()`, or until
|
189 | the [timeout](http://mochajs.org/#timeouts) expires. `done` also accepts an
|
190 | error parameter when signaling completion.
|
191 |
|
192 | #### Dealing with the response - Promises
|
193 |
|
194 | If `Promise` is available, `request()` becomes a Promise capable library -
|
195 | and chaining of `then`s becomes possible:
|
196 |
|
197 | ```js
|
198 | chai.request(app)
|
199 | .put('/user/me')
|
200 | .send({ password: '123', confirmPassword: '123' })
|
201 | .then(function (res) {
|
202 | expect(res).to.have.status(200);
|
203 | })
|
204 | .catch(function (err) {
|
205 | throw err;
|
206 | });
|
207 | ```
|
208 |
|
209 | __Note:__ Node.js version 0.10.x and some older web browsers do not have
|
210 | native promise support. You can use any spec compliant library, such as:
|
211 | - [kriskowal/q](https://github.com/kriskowal/q)
|
212 | - [stefanpenner/es6-promise](https://github.com/stefanpenner/es6-promise)
|
213 | - [petkaantonov/bluebird](https://github.com/petkaantonov/bluebird)
|
214 | - [then/promise](https://github.com/then/promise)
|
215 | You will need to set the library you use to `global.Promise`, before
|
216 | requiring in chai-http. For example:
|
217 |
|
218 | ```js
|
219 | // Add promise support if this does not exist natively.
|
220 | if (!global.Promise) {
|
221 | global.Promise = require('q');
|
222 | }
|
223 | var chai = require('chai');
|
224 | chai.use(require('chai-http'));
|
225 | ```
|
226 |
|
227 | #### Retaining cookies with each request
|
228 |
|
229 | Sometimes you need to keep cookies from one request, and send them with the
|
230 | next (for example, when you want to login with the first request, then access an authenticated-only resource later). For this, `.request.agent()` is available:
|
231 |
|
232 | ```js
|
233 | // Log in
|
234 | var agent = chai.request.agent(app)
|
235 | agent
|
236 | .post('/session')
|
237 | .send({ username: 'me', password: '123' })
|
238 | .then(function (res) {
|
239 | expect(res).to.have.cookie('sessionid');
|
240 | // The `agent` now has the sessionid cookie saved, and will send it
|
241 | // back to the server in the next request:
|
242 | return agent.get('/user/me')
|
243 | .then(function (res) {
|
244 | expect(res).to.have.status(200);
|
245 | });
|
246 | });
|
247 | ```
|
248 |
|
249 | Note: The server started by `chai.request.agent(app)` will not automatically close following the test(s). You should call `agent.close()` after your tests to ensure your program exits.
|
250 |
|
251 | ## Assertions
|
252 |
|
253 | The Chai HTTP module provides a number of assertions
|
254 | for the `expect` and `should` interfaces.
|
255 |
|
256 | ### .status (code)
|
257 |
|
258 | * **@param** _{Number}_ status number
|
259 |
|
260 | Assert that a response has a supplied status.
|
261 |
|
262 | ```js
|
263 | expect(res).to.have.status(200);
|
264 | ```
|
265 |
|
266 | ### .header (key[, value])
|
267 |
|
268 | * **@param** _{String}_ header key (case insensitive)
|
269 | * **@param** _{String|RegExp}_ header value (optional)
|
270 |
|
271 | Assert that a `Response` or `Request` object has a header.
|
272 | If a value is provided, equality to value will be asserted.
|
273 | You may also pass a regular expression to check.
|
274 |
|
275 | __Note:__ When running in a web browser, the
|
276 | [same-origin policy](https://tools.ietf.org/html/rfc6454#section-3)
|
277 | only allows Chai HTTP to read
|
278 | [certain headers](https://www.w3.org/TR/cors/#simple-response-header),
|
279 | which can cause assertions to fail.
|
280 |
|
281 | ```js
|
282 | expect(req).to.have.header('x-api-key');
|
283 | expect(req).to.have.header('content-type', 'text/plain');
|
284 | expect(req).to.have.header('content-type', /^text/);
|
285 | ```
|
286 |
|
287 | ### .headers
|
288 |
|
289 |
|
290 | Assert that a `Response` or `Request` object has headers.
|
291 |
|
292 | __Note:__ When running in a web browser, the
|
293 | [same-origin policy](https://tools.ietf.org/html/rfc6454#section-3)
|
294 | only allows Chai HTTP to read
|
295 | [certain headers](https://www.w3.org/TR/cors/#simple-response-header),
|
296 | which can cause assertions to fail.
|
297 |
|
298 | ```js
|
299 | expect(req).to.have.headers;
|
300 | ```
|
301 |
|
302 | ### .ip
|
303 |
|
304 |
|
305 | Assert that a string represents valid ip address.
|
306 |
|
307 | ```js
|
308 | expect('127.0.0.1').to.be.an.ip;
|
309 | expect('2001:0db8:85a3:0000:0000:8a2e:0370:7334').to.be.an.ip;
|
310 | ```
|
311 |
|
312 | ### .json / .text / .html
|
313 |
|
314 |
|
315 | Assert that a `Response` or `Request` object has a given content-type.
|
316 |
|
317 | ```js
|
318 | expect(req).to.be.json;
|
319 | expect(req).to.be.html;
|
320 | expect(req).to.be.text;
|
321 | ```
|
322 |
|
323 | ### .redirect
|
324 |
|
325 |
|
326 | Assert that a `Response` object has a redirect status code.
|
327 |
|
328 | ```js
|
329 | expect(res).to.redirect;
|
330 | expect(res).to.not.redirect;
|
331 | ```
|
332 |
|
333 | ### .redirectTo
|
334 |
|
335 | * **@param** _{String|RegExp}_ location url
|
336 |
|
337 | Assert that a `Response` object redirects to the supplied location.
|
338 |
|
339 | ```js
|
340 | expect(res).to.redirectTo('http://example.com');
|
341 | expect(res).to.redirectTo(/^\/search\/results\?orderBy=desc$/);
|
342 | ```
|
343 |
|
344 | ### .param
|
345 |
|
346 | * **@param** _{String}_ parameter name
|
347 | * **@param** _{String}_ parameter value
|
348 |
|
349 | Assert that a `Request` object has a query string parameter with a given
|
350 | key, (optionally) equal to value
|
351 |
|
352 | ```js
|
353 | expect(req).to.have.param('orderby');
|
354 | expect(req).to.have.param('orderby', 'date');
|
355 | expect(req).to.not.have.param('limit');
|
356 | ```
|
357 |
|
358 | ### .cookie
|
359 |
|
360 | * **@param** _{String}_ parameter name
|
361 | * **@param** _{String}_ parameter value
|
362 |
|
363 | Assert that a `Request` or `Response` object has a cookie header with a
|
364 | given key, (optionally) equal to value
|
365 |
|
366 | ```js
|
367 | expect(req).to.have.cookie('session_id');
|
368 | expect(req).to.have.cookie('session_id', '1234');
|
369 | expect(req).to.not.have.cookie('PHPSESSID');
|
370 | expect(res).to.have.cookie('session_id');
|
371 | expect(res).to.have.cookie('session_id', '1234');
|
372 | expect(res).to.not.have.cookie('PHPSESSID');
|
373 | ```
|
374 |
|
375 | ## License
|
376 |
|
377 | (The MIT License)
|
378 |
|
379 | Copyright (c) Jake Luer <jake@alogicalparadox.com>
|
380 |
|
381 | Permission is hereby granted, free of charge, to any person obtaining a copy
|
382 | of this software and associated documentation files (the "Software"), to deal
|
383 | in the Software without restriction, including without limitation the rights
|
384 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
385 | copies of the Software, and to permit persons to whom the Software is
|
386 | furnished to do so, subject to the following conditions:
|
387 |
|
388 | The above copyright notice and this permission notice shall be included in
|
389 | all copies or substantial portions of the Software.
|
390 |
|
391 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
392 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
393 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
394 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
395 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
396 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
397 | THE SOFTWARE.
|
398 |
|