UNPKG

16.1 kBJavaScriptView Raw
1
2/*!
3 * Should
4 * Copyright(c) 2010-2012 TJ Holowaychuk <tj@vision-media.ca>
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
12var util = require('util')
13 , http = require('http')
14 , assert = require('assert')
15 , AssertionError = assert.AssertionError
16 , statusCodes = http.STATUS_CODES
17 , eql = require('./eql')
18 , i = util.inspect;
19
20/**
21 * Expose assert as should.
22 *
23 * This allows you to do things like below
24 * without require()ing the assert module.
25 *
26 * should.equal(foo.bar, undefined);
27 *
28 */
29
30exports = module.exports = assert;
31
32/**
33 * Library version.
34 */
35
36exports.version = '1.0.0';
37
38/**
39 * Assert _obj_ exists, with optional message.
40 *
41 * @param {Mixed} obj
42 * @param {String} [msg]
43 * @api public
44 */
45
46exports.exist = function(obj, msg){
47 if (null == obj) {
48 throw new AssertionError({
49 message: msg || ('expected ' + i(obj) + ' to exist')
50 , stackStartFunction: should.exist
51 });
52 }
53};
54
55/**
56 * Asserts _obj_ does not exist, with optional message.
57 *
58 * @param {Mixed} obj
59 * @param {String} [msg]
60 * @api public
61 */
62
63exports.not = {};
64exports.not.exist = function(obj, msg){
65 if (null != obj) {
66 throw new AssertionError({
67 message: msg || ('expected ' + i(obj) + ' to not exist')
68 , stackStartFunction: should.not.exist
69 });
70 }
71};
72
73/**
74 * Expose api via `Object#should`.
75 *
76 * @api public
77 */
78
79Object.defineProperty(Object.prototype, 'should', {
80 set: function(){},
81 get: function(){
82 return new Assertion(Object(this).valueOf());
83 },
84 configurable: true
85});
86
87/**
88 * Initialize a new `Assertion` with the given _obj_.
89 *
90 * @param {Mixed} obj
91 * @api private
92 */
93
94var Assertion = exports.Assertion = function Assertion(obj) {
95 this.obj = obj;
96};
97
98/**
99 * Prototype.
100 */
101
102Assertion.prototype = {
103
104 /**
105 * HACK: prevents double require() from failing.
106 */
107
108 exports: exports,
109
110 /**
111 * Assert _expr_ with the given _msg_ and _negatedMsg_.
112 *
113 * @param {Boolean} expr
114 * @param {String} msg
115 * @param {String} negatedMsg
116 * @param {Object} expected
117 * @api private
118 */
119
120 assert: function(expr, msg, negatedMsg, expected){
121 var msg = this.negate ? negatedMsg : msg
122 , ok = this.negate ? !expr : expr;
123
124 if (!ok) {
125 throw new AssertionError({
126 message: msg
127 , actual: this.obj
128 , expected: expected
129 , stackStartFunction: this.assert
130 });
131 }
132 },
133
134 /**
135 * Dummy getter.
136 *
137 * @api public
138 */
139
140 get an() {
141 return this;
142 },
143
144 /**
145 * Dummy getter.
146 *
147 * @api public
148 */
149
150 get and() {
151 return this;
152 },
153
154 /**
155 * Dummy getter.
156 *
157 * @api public
158 */
159
160 get be() {
161 return this;
162 },
163
164 /**
165 * Dummy getter.
166 *
167 * @api public
168 */
169
170 get have() {
171 return this;
172 },
173
174 /**
175 * Dummy getter.
176 *
177 * @api public
178 */
179
180 get with() {
181 return this;
182 },
183
184 /**
185 * Negation modifier.
186 *
187 * @api public
188 */
189
190 get not() {
191 this.negate = true;
192 return this;
193 },
194
195 /**
196 * Get object inspection string.
197 *
198 * @return {String}
199 * @api private
200 */
201
202 get inspect() {
203 return i(this.obj);
204 },
205
206 /**
207 * Assert instanceof `Arguments`.
208 *
209 * @api public
210 */
211
212 get arguments() {
213 this.assert(
214 '[object Arguments]' == Object.prototype.toString.call(this.obj)
215 , 'expected ' + this.inspect + ' to be arguments'
216 , 'expected ' + this.inspect + ' to not be arguments');
217 return this;
218 },
219
220 /**
221 * Assert that an object is empty aka length of 0.
222 *
223 * @api public
224 */
225
226 get empty() {
227 this.obj.should.have.property('length');
228 this.assert(
229 0 === this.obj.length
230 , 'expected ' + this.inspect + ' to be empty'
231 , 'expected ' + this.inspect + ' not to be empty');
232 return this;
233 },
234
235 /**
236 * Assert ok.
237 *
238 * @api public
239 */
240
241 get ok() {
242 this.assert(
243 this.obj
244 , 'expected ' + this.inspect + ' to be truthy'
245 , 'expected ' + this.inspect + ' to be falsey');
246 return this;
247 },
248
249 /**
250 * Assert true.
251 *
252 * @api public
253 */
254
255 get true() {
256 this.assert(
257 true === this.obj
258 , 'expected ' + this.inspect + ' to be true'
259 , 'expected ' + this.inspect + ' not to be true');
260 return this;
261 },
262
263 /**
264 * Assert false.
265 *
266 * @api public
267 */
268
269 get false() {
270 this.assert(
271 false === this.obj
272 , 'expected ' + this.inspect + ' to be false'
273 , 'expected ' + this.inspect + ' not to be false');
274 return this;
275 },
276
277 /**
278 * Assert equal.
279 *
280 * @param {Mixed} val
281 * @param {String} description
282 * @api public
283 */
284
285 eql: function(val, desc){
286 this.assert(
287 eql(val, this.obj)
288 , 'expected ' + this.inspect + ' to equal ' + i(val) + (desc ? " | " + desc : "")
289 , 'expected ' + this.inspect + ' to not equal ' + i(val) + (desc ? " | " + desc : "")
290 , val);
291 return this;
292 },
293
294 /**
295 * Assert strict equal.
296 *
297 * @param {Mixed} val
298 * @param {String} description
299 * @api public
300 */
301
302 equal: function(val, desc){
303 this.assert(
304 val.valueOf() === this.obj
305 , 'expected ' + this.inspect + ' to equal ' + i(val) + (desc ? " | " + desc : "")
306 , 'expected ' + this.inspect + ' to not equal ' + i(val) + (desc ? " | " + desc : "")
307 , val);
308 return this;
309 },
310
311 /**
312 * Assert within start to finish (inclusive).
313 *
314 * @param {Number} start
315 * @param {Number} finish
316 * @param {String} description
317 * @api public
318 */
319
320 within: function(start, finish, desc){
321 var range = start + '..' + finish;
322 this.assert(
323 this.obj >= start && this.obj <= finish
324 , 'expected ' + this.inspect + ' to be within ' + range + (desc ? " | " + desc : "")
325 , 'expected ' + this.inspect + ' to not be within ' + range + (desc ? " | " + desc : ""));
326 return this;
327 },
328
329 /**
330 * Assert typeof.
331 *
332 * @param {Mixed} type
333 * @param {String} description
334 * @api public
335 */
336
337 a: function(type, desc){
338 this.assert(
339 type == typeof this.obj
340 , 'expected ' + this.inspect + ' to be a ' + type + (desc ? " | " + desc : "")
341 , 'expected ' + this.inspect + ' not to be a ' + type + (desc ? " | " + desc : ""));
342 return this;
343 },
344
345 /**
346 * Assert instanceof.
347 *
348 * @param {Function} constructor
349 * @param {String} description
350 * @api public
351 */
352
353 instanceof: function(constructor, desc){
354 var name = constructor.name;
355 this.assert(
356 this.obj instanceof constructor
357 , 'expected ' + this.inspect + ' to be an instance of ' + name + (desc ? " | " + desc : "")
358 , 'expected ' + this.inspect + ' not to be an instance of ' + name + (desc ? " | " + desc : ""));
359 return this;
360 },
361
362 /**
363 * Assert numeric value above _n_.
364 *
365 * @param {Number} n
366 * @param {String} description
367 * @api public
368 */
369
370 above: function(n, desc){
371 this.assert(
372 this.obj > n
373 , 'expected ' + this.inspect + ' to be above ' + n + (desc ? " | " + desc : "")
374 , 'expected ' + this.inspect + ' to be below ' + n + (desc ? " | " + desc : ""));
375 return this;
376 },
377
378 /**
379 * Assert numeric value below _n_.
380 *
381 * @param {Number} n
382 * @param {String} description
383 * @api public
384 */
385
386 below: function(n, desc){
387 this.assert(
388 this.obj < n
389 , 'expected ' + this.inspect + ' to be below ' + n + (desc ? " | " + desc : "")
390 , 'expected ' + this.inspect + ' to be above ' + n + (desc ? " | " + desc : ""));
391 return this;
392 },
393
394 /**
395 * Assert string value matches _regexp_.
396 *
397 * @param {RegExp} regexp
398 * @param {String} description
399 * @api public
400 */
401
402 match: function(regexp, desc){
403 this.assert(
404 regexp.exec(this.obj)
405 , 'expected ' + this.inspect + ' to match ' + regexp + (desc ? " | " + desc : "")
406 , 'expected ' + this.inspect + ' not to match ' + regexp + (desc ? " | " + desc : ""));
407 return this;
408 },
409
410 /**
411 * Assert property "length" exists and has value of _n_.
412 *
413 * @param {Number} n
414 * @param {String} description
415 * @api public
416 */
417
418 length: function(n, desc){
419 this.obj.should.have.property('length');
420 var len = this.obj.length;
421 this.assert(
422 n == len
423 , 'expected ' + this.inspect + ' to have a length of ' + n + ' but got ' + len + (desc ? " | " + desc : "")
424 , 'expected ' + this.inspect + ' to not have a length of ' + len + (desc ? " | " + desc : ""));
425 return this;
426 },
427
428 /**
429 * Assert property _name_ exists, with optional _val_.
430 *
431 * @param {String} name
432 * @param {Mixed} [val]
433 * @param {String} description
434 * @api public
435 */
436
437 property: function(name, val, desc){
438 if (this.negate && undefined !== val) {
439 if (undefined === this.obj[name]) {
440 throw new Error(this.inspect + ' has no property ' + i(name) + (desc ? " | " + desc : ""));
441 }
442 } else {
443 this.assert(
444 undefined !== this.obj[name]
445 , 'expected ' + this.inspect + ' to have a property ' + i(name) + (desc ? " | " + desc : "")
446 , 'expected ' + this.inspect + ' to not have a property ' + i(name) + (desc ? " | " + desc : ""));
447 }
448
449 if (undefined !== val) {
450 this.assert(
451 val === this.obj[name]
452 , 'expected ' + this.inspect + ' to have a property ' + i(name)
453 + ' of ' + i(val) + ', but got ' + i(this.obj[name]) + (desc ? " | " + desc : "")
454 , 'expected ' + this.inspect + ' to not have a property ' + i(name) + ' of ' + i(val) + (desc ? " | " + desc : ""));
455 }
456
457 this.obj = this.obj[name];
458 return this;
459 },
460
461 /**
462 * Assert own property _name_ exists.
463 *
464 * @param {String} name
465 * @param {String} description
466 * @api public
467 */
468
469 ownProperty: function(name, desc){
470 this.assert(
471 this.obj.hasOwnProperty(name)
472 , 'expected ' + this.inspect + ' to have own property ' + i(name) + (desc ? " | " + desc : "")
473 , 'expected ' + this.inspect + ' to not have own property ' + i(name) + (desc ? " | " + desc : ""));
474 return this;
475 },
476
477 /**
478 * Assert that `obj` is present via `.indexOf()`.
479 *
480 * @param {Mixed} obj
481 * @param {String} description
482 * @api public
483 */
484
485 include: function(obj, desc){
486 if (obj.constructor == Object){
487 var cmp = {};
488 for (var key in obj) cmp[key] = this.obj[key];
489 this.assert(
490 eql(cmp, obj)
491 , 'expected ' + this.inspect + ' to include an object equal to ' + i(obj) + (desc ? " | " + desc : "")
492 , 'expected ' + this.inspect + ' to not include an object equal to ' + i(obj) + (desc ? " | " + desc : ""));
493 } else {
494 this.assert(
495 ~this.obj.indexOf(obj)
496 , 'expected ' + this.inspect + ' to include ' + i(obj) + (desc ? " | " + desc : "")
497 , 'expected ' + this.inspect + ' to not include ' + i(obj) + (desc ? " | " + desc : ""));
498 }
499 return this;
500 },
501
502 /**
503 * Assert that an object equal to `obj` is present.
504 *
505 * @param {Array} obj
506 * @param {String} description
507 * @api public
508 */
509
510 includeEql: function(obj, desc){
511 this.assert(
512 this.obj.some(function(item) { return eql(obj, item); })
513 , 'expected ' + this.inspect + ' to include an object equal to ' + i(obj) + (desc ? " | " + desc : "")
514 , 'expected ' + this.inspect + ' to not include an object equal to ' + i(obj) + (desc ? " | " + desc : ""));
515 return this;
516 },
517
518 /**
519 * Assert that the array contains _obj_.
520 *
521 * @param {Mixed} obj
522 * @api public
523 */
524
525 contain: function(obj){
526 console.warn('should.contain() is deprecated, use should.include()');
527 this.obj.should.be.an.instanceof(Array);
528 this.assert(
529 ~this.obj.indexOf(obj)
530 , 'expected ' + this.inspect + ' to contain ' + i(obj)
531 , 'expected ' + this.inspect + ' to not contain ' + i(obj));
532 return this;
533 },
534
535 /**
536 * Assert exact keys or inclusion of keys by using
537 * the `.include` modifier.
538 *
539 * @param {Array|String ...} keys
540 * @api public
541 */
542
543 keys: function(keys){
544 var str
545 , ok = true;
546
547 keys = keys instanceof Array
548 ? keys
549 : Array.prototype.slice.call(arguments);
550
551 if (!keys.length) throw new Error('keys required');
552
553 var actual = Object.keys(this.obj)
554 , len = keys.length;
555
556 // make sure they're all present
557 ok = keys.every(function(key){
558 return ~actual.indexOf(key);
559 });
560
561 // matching length
562 ok = ok && keys.length == actual.length;
563
564 // key string
565 if (len > 1) {
566 keys = keys.map(function(key){
567 return i(key);
568 });
569 var last = keys.pop();
570 str = keys.join(', ') + ', and ' + last;
571 } else {
572 str = i(keys[0]);
573 }
574
575 // message
576 str = 'have ' + (len > 1 ? 'keys ' : 'key ') + str;
577
578 this.assert(
579 ok
580 , 'expected ' + this.inspect + ' to ' + str
581 , 'expected ' + this.inspect + ' to not ' + str);
582
583 return this;
584 },
585
586 /**
587 * Assert that header `field` has the given `val`.
588 *
589 * @param {String} field
590 * @param {String} val
591 * @return {Assertion} for chaining
592 * @api public
593 */
594
595 header: function(field, val){
596 this.obj.should
597 .have.property('headers').and
598 .have.property(field.toLowerCase(), val);
599 return this;
600 },
601
602 /**
603 * Assert `.statusCode` of `code`.
604 *
605 * @param {Number} code
606 * @return {Assertion} for chaining
607 * @api public
608 */
609
610 status: function(code){
611 this.obj.should.have.property('statusCode');
612 var status = this.obj.statusCode;
613
614 this.assert(
615 code == status
616 , 'expected response code of ' + code + ' ' + i(statusCodes[code])
617 + ', but got ' + status + ' ' + i(statusCodes[status])
618 , 'expected to not respond with ' + code + ' ' + i(statusCodes[code]));
619
620 return this;
621 },
622
623 /**
624 * Assert that this response has content-type: application/json.
625 *
626 * @return {Assertion} for chaining
627 * @api public
628 */
629
630 get json() {
631 this.obj.should.have.property('headers');
632 this.obj.headers.should.have.property('content-type');
633 this.obj.headers['content-type'].should.include('application/json');
634 return this;
635 },
636
637 /**
638 * Assert that this response has content-type: text/html.
639 *
640 * @return {Assertion} for chaining
641 * @api public
642 */
643
644 get html() {
645 this.obj.should.have.property('headers');
646 this.obj.headers.should.have.property('content-type');
647 this.obj.headers['content-type'].should.include('text/html');
648 return this;
649 },
650
651 /**
652 * Assert that this function will or will not
653 * throw an exception.
654 *
655 * @return {Assertion} for chaining
656 * @api public
657 */
658
659 throw: function(message){
660 var fn = this.obj
661 , err = {}
662 , errorInfo = ''
663 , ok = true;
664
665 try {
666 fn();
667 ok = false;
668 } catch (e) {
669 err = e;
670 }
671
672 if (ok) {
673 if ('string' == typeof message) {
674 ok = message == err.message;
675 } else if (message instanceof RegExp) {
676 ok = message.test(err.message);
677 } else if ('function' == typeof message) {
678 ok = err instanceof message;
679 }
680
681 if (message && !ok) {
682 if ('string' == typeof message) {
683 errorInfo = " with a message matching '" + message + "', but got '" + err.message + "'";
684 } else if (message instanceof RegExp) {
685 errorInfo = " with a message matching " + message + ", but got '" + err.message + "'";
686 } else if ('function' == typeof message) {
687 errorInfo = " of type " + message.name + ", but got " + err.constructor.name;
688 }
689 }
690 }
691
692 this.assert(
693 ok
694 , 'expected an exception to be thrown' + errorInfo
695 , 'expected no exception to be thrown, got "' + err.message + '"');
696
697 return this;
698 }
699};
700
701Assertion.prototype.instanceOf = Assertion.prototype.instanceof;
702Assertion.prototype.throwError = Assertion.prototype.throw;
703
704/**
705 * Aliases.
706 */
707
708(function alias(name, as){
709 Assertion.prototype[as] = Assertion.prototype[name];
710 return alias;
711})
712('length', 'lengthOf')
713('keys', 'key')
714('ownProperty', 'haveOwnProperty')
715('above', 'greaterThan')
716('below', 'lessThan');
717