1 | # Unexpected
|
2 |
|
3 | Extensible BDD assertion toolkit inspired by
|
4 | [expect.js](https://github.com/LearnBoost/expect.js)
|
5 |
|
6 | ```js
|
7 | expect(window.r, 'to be', undefined);
|
8 | expect({ a: 'b' }, 'to equal', { a: 'b' });
|
9 | expect(5, 'to be a', 'number');
|
10 | expect([], 'to be an', 'array');
|
11 | expect(window, 'not to be an', Image);
|
12 | ```
|
13 |
|
14 | [![NPM version](https://badge.fury.io/js/unexpected.png)](http://badge.fury.io/js/unexpected)
|
15 | [![Build Status](https://travis-ci.org/sunesimonsen/unexpected.svg?branch=master)](https://travis-ci.org/sunesimonsen/unexpected)
|
16 | [![Coverage Status](https://coveralls.io/repos/sunesimonsen/unexpected/badge.png)](https://coveralls.io/r/sunesimonsen/unexpected)
|
17 | [![Dependency Status](https://david-dm.org/sunesimonsen/unexpected.png)](https://david-dm.org/sunesimonsen/unexpected)
|
18 |
|
19 | [Run the test in the browser](http://sunesimonsen.github.io/unexpected/test/tests.html)
|
20 |
|
21 | ## Features
|
22 |
|
23 | - Extensible
|
24 | - Fast
|
25 | - Provides really nice error messages
|
26 | - Helps you if you misspells assertions
|
27 | - Compatible with all test frameworks.
|
28 | - Node.JS ready (`require('unexpected')`).
|
29 | - Single global with no prototype extensions or shims.
|
30 | - Cross-browser: works on Chrome, Firefox, Safari, Opera, IE6+,
|
31 | (IE6-IE8 with [es5-shim](https://github.com/es-shims/es5-shim)).
|
32 |
|
33 | ## How to use
|
34 |
|
35 | ### Node
|
36 |
|
37 | Install it with NPM or add it to your `package.json`:
|
38 |
|
39 | ```
|
40 | $ npm install unexpected
|
41 | ```
|
42 |
|
43 | Then:
|
44 |
|
45 | ```js
|
46 | var expect = require('unexpected');
|
47 | ```
|
48 |
|
49 | ### Browser
|
50 |
|
51 | Include `unexpected.js`.
|
52 |
|
53 | ```html
|
54 | <script src="unexpected.js"></script>
|
55 | ```
|
56 |
|
57 | this will expose the expect function under the following namespace:
|
58 |
|
59 | ```js
|
60 | var expect = weknowhow.expect;
|
61 | ```
|
62 |
|
63 | ### RequireJS
|
64 |
|
65 | Include the library with RequireJS the following way:
|
66 |
|
67 | ```js
|
68 | require.config({
|
69 | paths: {
|
70 | unexpected: 'path/to/unexpected'
|
71 | }
|
72 | });
|
73 |
|
74 | define(['unexpected'], function (expect) {
|
75 | // Your code
|
76 | });
|
77 | ```
|
78 |
|
79 | ## API
|
80 |
|
81 | ### to be ok
|
82 |
|
83 | asserts that the value is _truthy_
|
84 |
|
85 | **ok** / **truthy** / **falsy**: asserts that the value is _truthy_ or not
|
86 |
|
87 | ```js
|
88 | expect(1, 'to be ok');
|
89 | expect(true, 'to be ok');
|
90 | expect(true, 'not to be falsy');
|
91 | expect({}, 'to be truthy');
|
92 | expect(0, 'not to be ok');
|
93 | expect(0, 'to be falsy');
|
94 | expect(null, 'to be falsy');
|
95 | expect(undefined, 'to be falsy');
|
96 | ```
|
97 |
|
98 | **be**: asserts `===` equality
|
99 |
|
100 | ```js
|
101 | expect(obj, 'to be', obj);
|
102 | expect(obj, 'not to be', {});
|
103 | expect(1, 'to be', 1);
|
104 | expect(1, 'not to be', true);
|
105 | expect('1', 'not to be', 1);
|
106 | expect(null, 'not to be', undefined);
|
107 | expect(null, 'to be null');
|
108 | expect(0, 'not to be null');
|
109 | expect(undefined, 'not to be null');
|
110 | expect(true, 'to be true');
|
111 | expect(false, 'not to be true');
|
112 | expect(false, 'to be false');
|
113 | expect(true, 'not to be false');
|
114 | expect(undefined, 'to be undefined');
|
115 | expect(null, 'to be defined');
|
116 | expect(false, 'to be defined');
|
117 | expect({}, 'to be defined');
|
118 | ```
|
119 |
|
120 | **equal**: asserts deep equality that works with objects
|
121 |
|
122 | ```js
|
123 | expect({ a: 'b' }, 'to equal', { a: 'b' });
|
124 | expect(1, 'not to equal', '1');
|
125 | expect({ one: 1 }, 'not to equal', { one: '1' });
|
126 | expect(null, 'not to equal', '1');
|
127 | var now = new Date();
|
128 | expect(now, 'to equal', now);
|
129 | expect(now, 'to equal', new Date(now.getTime()));
|
130 | expect({ now: now }, 'to equal', { now: now });
|
131 | ```
|
132 |
|
133 | **canonical**: asserts that an object has its properties defined in sorted order at all levels
|
134 |
|
135 | ```js
|
136 | expect({ a: 123, b: 456 }, 'to be canonical');
|
137 | expect([456, { a: 123 }], 'to be canonical');
|
138 | ```
|
139 |
|
140 | **a** / **an**: asserts `typeof` with support for `array` type and `instanceof`
|
141 |
|
142 | ```js
|
143 | expect(5, 'to be a', 'number');
|
144 | expect(5, 'to be a number');
|
145 |
|
146 | expect('abc', 'to be a', 'string');
|
147 | expect('abc', 'to be a string');
|
148 | expect('', 'to be an empty string');
|
149 | expect('abc', 'to be a non-empty string');
|
150 |
|
151 | expect([], 'to be an', 'array');
|
152 | expect([], 'to be an array');
|
153 | expect([], 'to be an', Array);
|
154 | expect([], 'to be an empty array');
|
155 | expect([123], 'to be a non-empty array');
|
156 |
|
157 | expect({foo: 123}, 'to be an', 'object');
|
158 | expect({foo: 123}, 'to be an object');
|
159 | expect({foo: 123}, 'to be a non-empty object');
|
160 | expect({}, 'to be an empty object');
|
161 |
|
162 | expect(null, 'not to be an', 'object');
|
163 | expect(null, 'not to be an object');
|
164 |
|
165 | expect(true, 'to be a', 'boolean');
|
166 | expect(true, 'to be a boolean');
|
167 |
|
168 | expect(expect, 'to be a', 'function');
|
169 | expect(expect, 'to be a function');
|
170 | ```
|
171 |
|
172 | **NaN**: asserts that the value is `NaN`
|
173 |
|
174 | ```js
|
175 | expect(NaN, 'to be NaN');
|
176 | expect(2, 'not to be NaN');
|
177 | ```
|
178 |
|
179 | **close to**: asserts that the difference between two numbers is <= epsilon
|
180 |
|
181 | ```js
|
182 | expect(1.5, 'to be close to', 1.500001, 1e-5);
|
183 | expect(1.5, 'not to be close to', 1.499, 1e-4);
|
184 | ```
|
185 |
|
186 | epsilon defaults to 1e-9 if omitted:
|
187 |
|
188 | ```js
|
189 | expect(1.5, 'to be close to', 1.5000000001);
|
190 | ```
|
191 |
|
192 | **match**: asserts `String` regular expression match
|
193 |
|
194 | ```js
|
195 | expect('test', 'to match', /.*st/);
|
196 | expect('test', 'not to match', /foo/);
|
197 | expect(null, 'not to match', /foo/);
|
198 | ```
|
199 |
|
200 | **contain**: asserts indexOf for an array or string
|
201 |
|
202 | ```js
|
203 | expect([1, 2], 'to contain', 1);
|
204 | expect('hello world', 'to contain', 'world');
|
205 | ```
|
206 |
|
207 | **length**: asserts array `.length`
|
208 |
|
209 | ```js
|
210 | expect([], 'to have length', 0);
|
211 | expect([1,2,3], 'to have length', 3);
|
212 | expect([1,2,3], 'not to have length', 4);
|
213 | ```
|
214 |
|
215 | **empty**: asserts that an array or array-like object (identified by the presence of a `length` property) is empty
|
216 |
|
217 | ```js
|
218 | expect([], 'to be empty');
|
219 | expect('', 'to be empty');
|
220 | expect({ my: 'object' }, 'not to be empty');
|
221 | expect([1,2,3], 'not to be empty');
|
222 | expect({ length: 0, duck: 'typing' }, 'to be empty');
|
223 | ```
|
224 |
|
225 | **property**: asserts presence of an own property (and value optionally)
|
226 |
|
227 | ```js
|
228 | expect([1, 2], 'to have property', 'length');
|
229 | expect([1, 2], 'to have property', 'length', 2);
|
230 | expect({a: 'b'}, 'to have property', 'a');
|
231 | expect({a: 'b'}, 'to have property', 'a', 'b');
|
232 | expect({a: 'b'}, 'to have property', 'toString');
|
233 | expect({a: 'b'}, 'to have own property', 'a');
|
234 | expect(Object.create({a: 'b'}), 'not to have own property', 'a');
|
235 | ```
|
236 |
|
237 | **properties**: assert presence of properties in an object (and value optionally)
|
238 |
|
239 | ```js
|
240 | expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have properties', ['a', 'b']);
|
241 | expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have own properties', ['a', 'b']);
|
242 | expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'not to have properties', ['k', 'l']);
|
243 | expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have properties', {
|
244 | a: 'a',
|
245 | b: { c: 'c' }
|
246 | });
|
247 | expect([ 'a', { c: 'c' }, 'd' ], 'to have properties', {
|
248 | 1: { c: 'c' }
|
249 | 2: 'd',
|
250 | });
|
251 | ```
|
252 |
|
253 | **key** / **keys**: asserts the presence of a key. Supports the `only` modifier
|
254 |
|
255 | ```js
|
256 | expect({ a: 'b' }, 'to have key', 'a');
|
257 | expect({ a: 'b' }, 'not to have key', 'b');
|
258 | expect({ a: 'b', c: 'd' }, 'to not only have key', 'a');
|
259 | expect({ a: 'b', c: 'd' }, 'to only have keys', 'a', 'c');
|
260 | expect({ a: 'b', c: 'd' }, 'to only have keys', ['a', 'c']);
|
261 | expect({ a: 'b', c: 'd', e: 'f' }, 'to not only have keys', ['a', 'c']);
|
262 | ```
|
263 |
|
264 | **throw exception** / **throw error** / **throw**: asserts that the `Function` throws or not when called
|
265 |
|
266 | ```js
|
267 | expect(fn, 'to throw exception');
|
268 | expect(fn, 'to throw');
|
269 | expect(fn, 'to throw exception', function (e) { // get the exception object
|
270 | expect(e, 'to be a', SyntaxError);
|
271 | });
|
272 | expect(fn, 'to throw exception', /matches the exception message/);
|
273 | expect(fn, 'to throw error', 'matches the exact exception message');
|
274 | expect(fn2, 'not to throw error');
|
275 | ```
|
276 |
|
277 | **arity**: asserts that the `Function` takes the given number of arguments
|
278 |
|
279 | ```js
|
280 | expect(Math.max, 'to have arity', 2);
|
281 | ```
|
282 |
|
283 | **finite/infinite**: asserts a finite or infinite number
|
284 |
|
285 | ```js
|
286 | expect(123, 'to be finite');
|
287 | expect(Infinity, 'not to be finite');
|
288 | expect(Infinity, 'to be infinite');
|
289 | ```
|
290 |
|
291 | **within**: asserts a number within a range
|
292 |
|
293 | ```js
|
294 | expect(0, 'to be within', 0, 4);
|
295 | expect(1, 'to be within', 0, 4);
|
296 | expect(4, 'to be within', 0, 4);
|
297 | expect(-1, 'not to be within', 0, 4);
|
298 | expect(5, 'not to be within', 0, 4);
|
299 | ```
|
300 |
|
301 | **greater than** / **above**: asserts `>`
|
302 |
|
303 | ```js
|
304 | expect(3, 'to be greater than', 2);
|
305 | expect(1, 'to be above', 0);
|
306 | expect(4, 'to be >', 3);
|
307 | expect(4, '>', 3);
|
308 | ```
|
309 |
|
310 | **greater than or equal to**: asserts `>`
|
311 |
|
312 | ```js
|
313 | expect(3, 'to be greater than or equal to', 2);
|
314 | expect(3, 'to be >=', 3);
|
315 | expect(3, '>=', 3);
|
316 | ```
|
317 |
|
318 | **less than** / **below**: asserts `<`
|
319 |
|
320 | ```js
|
321 | expect(0, 'to be less than', 4);
|
322 | expect(0, 'to be below', 1);
|
323 | expect(3, 'to be <', 4);
|
324 | expect(3, '<', 4);
|
325 | ```
|
326 |
|
327 | **less than or equal to**: asserts `>`
|
328 |
|
329 | ```js
|
330 | expect(0, 'to be less than or equal to', 4);
|
331 | expect(4, 'to be <=', 4);
|
332 | expect(3, '<=', 4);
|
333 | ```
|
334 |
|
335 | **positive**: assert that a number is positive
|
336 |
|
337 | ```js
|
338 | expect(3, 'to be positive');
|
339 | ```
|
340 |
|
341 | **negative**: assert that a number is negative
|
342 |
|
343 | ```js
|
344 | expect(-1, 'to be negative');
|
345 | ```
|
346 |
|
347 | **fail**: explicitly forces failure.
|
348 |
|
349 | ```js
|
350 | expect.fail()
|
351 | expect.fail('Custom failure message')
|
352 | expect.fail('{0} was expected to be {1}', 0, 'zero');
|
353 | ```
|
354 |
|
355 | I case you want to rethrow an error, you should always use
|
356 | `expect.fail`, as it ensures that the error message will be correct
|
357 | for the different error modes.
|
358 |
|
359 | ```js
|
360 | var error = new Error('throw me');
|
361 | expect.fail(new Error(error));
|
362 | ```
|
363 |
|
364 | When you want to build a completely custom output, you can call
|
365 | `expect.fail` with a callback and receive a
|
366 | [magicpen](https://github.com/sunesimonsen/magicpen) instance that the
|
367 | output can be written to.
|
368 |
|
369 | ```js
|
370 | expect.fail(function (output) {
|
371 | 'You have been a very bad boy!'.split(/ /).forEach(function (word, index) {
|
372 | if (index > 0) { output.sp(); }
|
373 | var style = index % 2 === 0 ? 'cyan' : 'magenta';
|
374 | output[style](word);
|
375 | });
|
376 | });
|
377 | ```
|
378 |
|
379 | <img src="./images/fail-custom.png" alt="Custom error output using expect.fail">
|
380 |
|
381 | **array whose items satisfy**: will run an assertion function for each items in an array
|
382 |
|
383 | ```js
|
384 | expect([0, 1, 2, 3, 4], 'to be an array whose items satisfy', function (item, index) {
|
385 | expect(item, 'to be a number');
|
386 | });
|
387 |
|
388 | expect([0, 1, 2, 3, 4], 'to be an array whose items satisfy', 'to be a number');
|
389 |
|
390 | expect([[1], [2]], 'to be an array whose items satisfy',
|
391 | 'to be an array whose items satisfy', 'to be a number');
|
392 |
|
393 | expect([[], []], 'to be a non-empty array whose items satisfy', function (item) {
|
394 | expect(item, 'to be an empty array');
|
395 | });
|
396 | ```
|
397 |
|
398 | Using this assertion result in very detailed error reporting as shown in the below example:
|
399 |
|
400 | ```js
|
401 | expect([[0, 1, 2], [4, '5', 6], [7, 8, '9']],
|
402 | 'to be an array whose items satisfy', function (arr) {
|
403 | expect(arr, 'to be an array whose items satisfy', function (item) {
|
404 | expect(item, 'to be a number');
|
405 | });
|
406 | });
|
407 | ```
|
408 |
|
409 | will output:
|
410 |
|
411 | ```
|
412 | failed expectation in [ [ 0, 1, 2 ], [ 4, '5', 6 ], [ 7, 8, '9' ] ]:
|
413 | 1: failed expectation in [ 4, '5', 6 ]:
|
414 | 1: expected '5' to be a number
|
415 | 2: failed expectation in [ 7, 8, '9' ]:
|
416 | 2: expected '9' to be a number
|
417 | ```
|
418 |
|
419 | **satisfy**: match against a spec
|
420 |
|
421 | All properties and nested objects mentioned in the right-hand side object are
|
422 | required to be present. Primitive values are compared with `to equal` semantics:
|
423 |
|
424 | ```js
|
425 | expect({ hey: { there: true } }, 'to satisfy', { hey: {} });
|
426 | ```
|
427 |
|
428 | To disallow additional properties in the subject, use `to exhaustively satisfy`:
|
429 |
|
430 | ```js
|
431 | expect({ hey: { there: true } }, 'to exhaustively satisfy', { hey: { there: true } });
|
432 | ```
|
433 |
|
434 | Regular expressions and functions in the right-hand side object will be run
|
435 | against the corresponding values in the subject:
|
436 |
|
437 | ```js
|
438 | expect({ bar: 'quux', baz: true }, 'to satisfy', { bar: /QU*X/i });
|
439 | ```
|
440 |
|
441 | Can be combined with `expect.it` to create complex specifications that delegate to
|
442 | existing assertions:
|
443 |
|
444 | ```js
|
445 | expect({foo: 123, bar: 'bar', baz: 'bogus', qux: 42}, 'to satisfy', {
|
446 | foo: expect.it('to be a number').and('to be greater than', 10),
|
447 | baz: expect.it('not to match', /^boh/),
|
448 | qux: expect.it('to be a string')
|
449 | .and('not to be empty')
|
450 | .or('to be a number')
|
451 | .and('to be positive')
|
452 | });
|
453 | ```
|
454 |
|
455 | **map whose keys satisfy**: will run an assertion function for each key in a map
|
456 |
|
457 |
|
458 | ```js
|
459 | expect({ foo: 0, bar: 1, baz: 2, qux: 3 },
|
460 | 'to be a map whose keys satisfy', function (key) {
|
461 | expect(key, 'to match', /^[a-z]{3}$/);
|
462 | });
|
463 |
|
464 | expect({ foo: 0, bar: 1, baz: 2, qux: 3 },
|
465 | 'to be a map whose keys satisfy',
|
466 | 'to match', /^[a-z]{3}$/);
|
467 | ```
|
468 |
|
469 | Using this assertion result in very detailed error reporting as shown in the below example:
|
470 |
|
471 | ```js
|
472 | expect({ foo: 0, bar: 1, baz: 2, qux: 3, quux: 4 },
|
473 | 'to be a map whose keys satisfy', function (key) {
|
474 | expect(key, 'to have length', 3);
|
475 | });
|
476 | ```
|
477 |
|
478 | will output:
|
479 |
|
480 | ```
|
481 | failed expectation on keys foo, bar, baz, qux, quux:
|
482 | quux: expected 'quux' to have length 3
|
483 | ```
|
484 |
|
485 | **map whose values satisfy**: will run an assertion function for each value in a map
|
486 |
|
487 | ```js
|
488 | expect({ foo: 0, bar: 1, baz: 2, qux: 3 },
|
489 | 'to be a map whose values satisfy', function (value) {
|
490 | expect(value, 'to be a number');
|
491 | });
|
492 |
|
493 | expect({ foo: 0, bar: 1, baz: 2, qux: 3 },
|
494 | 'to be a map whose values satisfy',
|
495 | 'to be a number');
|
496 | ```
|
497 |
|
498 | Using this assertion result in very detailed error reporting as shown in the below example:
|
499 |
|
500 | ```js
|
501 | expect({ foo: [0, 1, 2], bar: [4, '5', 6], baz: [7, 8, '9'] },
|
502 | 'to be a map whose values satisfy', function (arr) {
|
503 | expect(arr, 'to be an array whose items satisfy', function (item) {
|
504 | expect(item, 'to be a number');
|
505 | });
|
506 | });
|
507 | ```
|
508 |
|
509 | will output:
|
510 |
|
511 | ```
|
512 | failed expectation in
|
513 | {
|
514 | foo: [ 0, 1, 2 ],
|
515 | bar: [ 4, '5', 6 ],
|
516 | baz: [ 7, 8, '9' ]
|
517 | }:
|
518 | bar: failed expectation in [ 4, '5', 6 ]:
|
519 | 1: expected '5' to be a number
|
520 | baz: failed expectation in [ 7, 8, '9' ]:
|
521 | 2: expected '9' to be a number
|
522 | ```
|
523 |
|
524 | ## Extending Unexpected with new assertions
|
525 |
|
526 | ### expect.clone()
|
527 |
|
528 | Before extending the `expect` instance with new assertions it is
|
529 | usually a good idea to clone it, so you don't change the global
|
530 | instance. You do that by calling the `clone` method on `expect`.
|
531 | Adding new assertions to the clone will not affect the original
|
532 | instance.
|
533 |
|
534 | ### expect.addAssertion([typeName, ...], [pattern, ...], handler)
|
535 |
|
536 | Signature:
|
537 |
|
538 | ```js
|
539 | expect.addAssertion(pattern, handler);
|
540 | expect.addAssertion([pattern, ...]], handler);
|
541 | expect.addAssertion(typeName, pattern, handler);
|
542 | expect.addAssertion(typeName, [pattern, ...], handler);
|
543 | expect.addAssertion([typeName, ...], pattern, handler);
|
544 | expect.addAssertion([typeName, ...], [pattern, ...], handler);
|
545 | ```
|
546 |
|
547 | New assertions can be added to Unexpected to following way.
|
548 |
|
549 | ```js
|
550 | expect.addAssertion('array', '[not] to be (sorted|ordered)', function(expect, subject, cmp) {
|
551 | expect(subject, '[not] to equal', [].concat(subject).sort(cmp));
|
552 | });
|
553 |
|
554 | ```
|
555 |
|
556 | The above assertion definition makes the following expects possible:
|
557 |
|
558 | ```js
|
559 | expect([1,2,3], 'to be sorted');
|
560 | expect([1,2,3], 'to be ordered');
|
561 | expect([2,1,3], 'not to be sorted');
|
562 | expect([2,1,3], 'not to be ordered');
|
563 | expect([3,2,1], 'to be sorted', function (x, y) { return y - x; });
|
564 | ```
|
565 |
|
566 | Let's dissect the different parts of the custom assertion we just
|
567 | introduced.
|
568 |
|
569 | The first parameter to `addAssertion` is a string or an array
|
570 | specifying which types the assertion should be defined on. In this
|
571 | case the assertion in only defined for arrays. In case the type is not
|
572 | specified the assertion will be defined for the type `any`, and would
|
573 | be applicable any type. See the `Extending Unexpected with new types`
|
574 | section for more information about the type system in Unexpected.
|
575 |
|
576 | The second parameter to `addAssertion` is a string or an array stating
|
577 | the patterns this assertion should match. A pattern has the following
|
578 | syntax. A word in square brackets represents a flag that can either be
|
579 | there or not. If the flag is present `this.flags[flag]` will contain
|
580 | the value `true`. In this case `not` is a flag. When a flag it present
|
581 | in a nested `expect` it will be inserted if the flag is present;
|
582 | otherwise it will be removed. Text that is in parentheses with
|
583 | vertical bars between them are treated as alternative texts that can
|
584 | be used. In this case you can write _ordered_ as an alternative to
|
585 | _sorted_.
|
586 |
|
587 | The last parameter to `addAssertion` is function that will be called
|
588 | when `expect` is invoked with an expectation matching the type and
|
589 | pattern of the assertion.
|
590 |
|
591 | So in this case, when `expect` is called the following way:
|
592 |
|
593 | ```js
|
594 | expect([3,2,1], 'to be sorted', reverse);
|
595 | ```
|
596 |
|
597 | The handler to our assertion will be called with the values the
|
598 | following way, where the _not_ flag in the nested expect will be
|
599 | removed:
|
600 |
|
601 | ```js
|
602 | expect.addAssertion('[not] to be (sorted|ordered)', function(expect, [3,2,1], reverse){
|
603 | expect([3,2,1], '[not] to equal', [].concat([3,2,1]).sort(reverse));
|
604 | });
|
605 | ```
|
606 |
|
607 | #### Controlling the output of nested expects
|
608 |
|
609 | When a call to `expect` fails inside your assertion the standard error
|
610 | message for the custom assertion will be used. In the case of our
|
611 | _sorted_ assertion the output will be something along the lines:
|
612 |
|
613 | ```
|
614 | expected [ 4, 3, 1, 2 ] to be sorted
|
615 | ```
|
616 |
|
617 | We can control the output of the nested expects by using the `this.errorMode`
|
618 | flag.
|
619 |
|
620 | ```js
|
621 | expect.addAssertion('[not] to be (sorted|ordered)', function(expect, subject, cmp) {
|
622 | this.errorMode = 'bubble';
|
623 | expect(subject, '[not] to equal', [].concat(subject).sort(cmp));
|
624 | });
|
625 |
|
626 | ```
|
627 |
|
628 | This will change the error output to:
|
629 |
|
630 | ```
|
631 | expected [ 4, 3, 1, 2 ] to equal [ 1, 2, 3, 4 ]
|
632 | ```
|
633 |
|
634 | If we change the error mode to _nested_, we get the following:
|
635 |
|
636 | ```
|
637 | expected [ 4, 3, 1, 2 ] to be sorted
|
638 | expected [ 4, 3, 1, 2 ] to equal [ 1, 2, 3, 4 ]
|
639 | ```
|
640 |
|
641 | The best resource for learning more about custom assertions is to look
|
642 | at how the predefined assertions are build:
|
643 |
|
644 | [lib/assertions.js](https://github.com/sunesimonsen/unexpected/blob/master/lib/assertions.js)
|
645 |
|
646 | ## Types
|
647 |
|
648 | Unexpected comes with a type system that is used to explain how
|
649 | different types are compared, diffed, inspected and is also used to
|
650 | limit the scope of assertions.
|
651 |
|
652 | The following types are provided by out of the box by Unexpected:
|
653 | `any`, `arguments`, `array`, `arrayLike`, `binaryArray`, `boolean`,
|
654 | `Buffer`, `date`, `Error`, `function`, `null`, `number`, `object`,
|
655 | `regexp`, `string`, `undefined`.
|
656 |
|
657 | ### expect.addType(typeDefinition)
|
658 |
|
659 | Unexpected can be extended with knowledge about new types by calling
|
660 | the `addType` method with a type definition. The type definition must
|
661 | implement the required parts of the following interface:
|
662 |
|
663 | Required members:
|
664 |
|
665 | * __name__: `String` - the name of the type.
|
666 | * __identify__: `boolean function(value)` - a function deciding if the type
|
667 | should be used for the given value.
|
668 |
|
669 | Optional members:
|
670 |
|
671 | * __base__: `String` - the name of the base type. Defaults to `any`.
|
672 | * __equal__: `boolean function(a, b, equal)` -
|
673 | a function capable of comparing two values of this type for
|
674 | equality. If not specified it is inherited from the base type.
|
675 | * __inspect__: `function(value, depth, output, inspect)` -
|
676 | a function capable of inspecting a value of this type. If not
|
677 | specified it is inherited from the base type.
|
678 | * __diff__: `comparison function(actual, expected, output, diff, inspect)` -
|
679 | a function producing a comparison between two values of this
|
680 | type. If not specified it is inherited from the base type.
|
681 |
|
682 | #### Example
|
683 |
|
684 | Adding new types to the system is best explained by an example. Let's
|
685 | say we wanted to add first class support for a `Person` type:
|
686 |
|
687 | ```js
|
688 | function Person(name, age) {
|
689 | this.name = name;
|
690 | this.age = age;
|
691 | }
|
692 | ```
|
693 |
|
694 | We start out by creating a basic type for handling `Person`
|
695 | instances. The name of the type should be `Person` and it should
|
696 | inherit from the build in `object` type. Furthermore we add an
|
697 | `identify` method that will recognize `Person` instances.
|
698 |
|
699 | ```js
|
700 | expect.addType({
|
701 | name: 'Person',
|
702 | base: 'object',
|
703 | identify: function (value) {
|
704 | return value instanceof Person;
|
705 | }
|
706 | });
|
707 | ```
|
708 |
|
709 | When you specify a base type, you inherit the optional members you
|
710 | didn't implement. In this case we inherited the methods `equal`,
|
711 | `inspect` and `diff` from the `object` type.
|
712 |
|
713 | Imagine that we make a failing expectation on a person instance:
|
714 |
|
715 | ```js
|
716 | expect(new Person('John Doe', 42), 'to equal', new Person('Jane Doe', 24));
|
717 | ```
|
718 |
|
719 | the result is the following output:
|
720 |
|
721 | <img src="./images/addType-basic.png" alt="Failed expectation of basic type">
|
722 |
|
723 | That is already quite helpful, but the output misses the information
|
724 | that it is `Person` instances we are comparing. We can fix that by
|
725 | implementing an `inspect` method on the type.
|
726 |
|
727 | ```js
|
728 | expect.addType({
|
729 | name: 'Person',
|
730 | base: 'object',
|
731 | identify: function (value) {
|
732 | return value instanceof Person;
|
733 | },
|
734 | inspect: function (person, depth, output, inspect) {
|
735 | output.text('new Person(')
|
736 | .append(inspect(person.name, depth))
|
737 | .text(', ')
|
738 | .append(inspect(person.age, depth))
|
739 | .text(')');
|
740 | }
|
741 | });
|
742 | ```
|
743 |
|
744 | Now we get the following output:
|
745 |
|
746 | <img src="./images/addType-inspect.png" alt="Failed expectation of type with inspect defined">
|
747 |
|
748 | That is a bit better, let me explain how it works. The `inspect`
|
749 | method is called with the value to be inspected, the depth this type
|
750 | should be inspected with, an output the inspected value should be
|
751 | written to and an inspect function that can be used to recursively
|
752 | inspect members. The output is an instance of
|
753 | [magicpen](https://github.com/sunesimonsen/magicpen) extended with a
|
754 | number of [styles](https://github.com/sunesimonsen/unexpected/blob/master/lib/styles.js).
|
755 |
|
756 | We write `new Person(` without styling, then we append the inspected
|
757 | `name`, write a `, `, inspect the `age` and finish with the closing
|
758 | parenthesis. When `inspect` is called without a depth parameter it
|
759 | defaults to `depth-1`. Values inspected with depth zero will be
|
760 | inspected as `...`. In this case we always want the name so we forward the
|
761 | same depth to the `inspect` function.
|
762 |
|
763 | Let's say we wanted persons only to be compared by name and not by
|
764 | age. Then we need to override the `equal` method:
|
765 |
|
766 | ```js
|
767 | expect.addType({
|
768 | name: 'Person',
|
769 | base: 'object',
|
770 | identify: function (value) {
|
771 | return value instanceof Person;
|
772 | },
|
773 | inspect: function (person, depth, output, inspect) {
|
774 | output.text('new Person(')
|
775 | .append(inspect(person.name, depth))
|
776 | .text(', ')
|
777 | .append(inspect(person.age, depth))
|
778 | .text(')');
|
779 | },
|
780 | equal: function (a, b, equal) {
|
781 | return a === b || equal(a.name, b.name);
|
782 | }
|
783 | });
|
784 | ```
|
785 |
|
786 | This will produce the same output as above, but that means the diff if
|
787 | wrong. It states that the age should be changed. We can fix that the
|
788 | following way:
|
789 |
|
790 | ```js
|
791 | expect.addType({
|
792 | name: 'Person',
|
793 | base: 'object',
|
794 | identify: function (value) {
|
795 | return value instanceof Person;
|
796 | },
|
797 | inspect: function (person, depth, output, inspect) {
|
798 | output.text('new Person(')
|
799 | .append(inspect(person.name, depth))
|
800 | .text(', ')
|
801 | .append(inspect(person.age, depth))
|
802 | .text(')');
|
803 | },
|
804 | equal: function (a, b, equal) {
|
805 | return a === b || equal(a.name, b.name);
|
806 | },
|
807 | diff: function (actual, expected, output, diff, inspect) {
|
808 | return this.baseType.diff({name: actual.name}, {name: expected.name});
|
809 | }
|
810 | });
|
811 | ```
|
812 |
|
813 | <img src="./images/addType-diff.png" alt="Failed expectation of type with diff defined">
|
814 |
|
815 | The above `diff` method just calls the `diff` method on the base type
|
816 | with objects that only contain the name. The `object` diff will take
|
817 | care of all the hard work. We could also have called the `diff`
|
818 | function we got as an argument, but that will go off detecting the
|
819 | types of the parameters, therefore it is faster to call `diff` method
|
820 | on the base directly when you know it is the one you need.
|
821 |
|
822 | You could also do something really custom as seen below:
|
823 |
|
824 | ```js
|
825 | expect.addType({
|
826 | name: 'Person',
|
827 | base: 'object',
|
828 | identify: function (value) {
|
829 | return value instanceof Person;
|
830 | },
|
831 | inspect: function (person, depth, output, inspect) {
|
832 | output.text('new Person(')
|
833 | .append(inspect(person.name, depth))
|
834 | .text(', ')
|
835 | .append(inspect(person.age, depth))
|
836 | .text(')');
|
837 | },
|
838 | equal: function (a, b, equal) {
|
839 | return a === b || equal(a.name, b.name);
|
840 | },
|
841 | diff: function (actual, expected, output, diff, inspect) {
|
842 | var nameDiff = diff(actual.name, expected.name);
|
843 |
|
844 | output.text('new Person(')
|
845 | .nl()
|
846 | .indentLines();
|
847 |
|
848 | if (nameDiff && nameDiff.inline) {
|
849 | output.append(nameDiff.diff);
|
850 | } else {
|
851 | output.i().append(inspect(actual.name)).text(',').sp()
|
852 | .annotationBlock(function () {
|
853 | this.error('should be ').append(inspect(expected.name));
|
854 | if (nameDiff) {
|
855 | this.nl().append(nameDiff.diff);
|
856 | }
|
857 | })
|
858 | .nl();
|
859 | }
|
860 |
|
861 | output.i().append(inspect(actual.age))
|
862 | .outdentLines()
|
863 | .nl()
|
864 | .text(')');
|
865 |
|
866 | return {
|
867 | inline: false,
|
868 | diff: output
|
869 | };
|
870 | }
|
871 | });
|
872 | ```
|
873 |
|
874 | That would produce the following output.
|
875 |
|
876 | <img src="./images/addType-improved-diff.png" alt="Failed expectation of type with an improved diff">
|
877 |
|
878 | This is a rather complicated example and I wont go though the details,
|
879 | but I would like to comment on the `inline` flag. When we diff objects
|
880 | against each other, the values of the keys will be diffed against each
|
881 | other. That means diffs are inserted into the containing
|
882 | structure. You can control this behavior using the `inline` flag. If
|
883 | the child diff is inline, it means that it will be appended directly
|
884 | into the parent; otherwise the diff will be inserted in an annotation
|
885 | block. The outputs below shows the contrast between setting the
|
886 | `Person` diff to inline or not.
|
887 |
|
888 | <img src="./images/addType-inline-diff.png" alt="Person diff inlining is on">
|
889 |
|
890 | <img src="./images/addType-inline-false-diff.png" alt="Person diff inlining is off">
|
891 |
|
892 | Now that we have implemented a type, we can start adding assertions to
|
893 | it. These assertions will only work on this type or types inheriting
|
894 | from the type.
|
895 |
|
896 | ```js
|
897 | expect.addAssertion('Person', 'to be above legal age', function (expect, subject) {
|
898 | expect(subject.age, 'to be greater than or equal to', 18);
|
899 | });
|
900 |
|
901 | expect(new Person('Jane Doe', 24), 'to be above legal age');
|
902 | ```
|
903 |
|
904 | Because `Person` inherits from `object` you can use all assertion
|
905 | defined for `object` or any of it's ancestors. Here is an example:
|
906 |
|
907 | ```js
|
908 | expect(new Person('Jane Doe', 24), 'to have keys', 'name', 'age');
|
909 | expect(new Person('Jane Doe', 24), 'to satisfy', {
|
910 | name: expect.it('to be a string').and('not to be empty'),
|
911 | age: expect.it('to be a number').and('not to be negative')
|
912 | });
|
913 | ```
|
914 |
|
915 | The best resource for learning more about custom types is to look at
|
916 | how the predefined types are build:
|
917 |
|
918 | [lib/types.js](https://github.com/sunesimonsen/unexpected/blob/master/lib/types.js)
|
919 |
|
920 | ## Plugins - expect.installPlugin(plugin)
|
921 |
|
922 | Unexpected plugins are objects that adhere to the following interface:
|
923 |
|
924 | ```js
|
925 | {
|
926 | name: <plugin name>,
|
927 | dependencies: <an optional list of dependencies>,
|
928 | installInto: <a function that will update the given expect instance>
|
929 | }
|
930 | ```
|
931 |
|
932 | The name of the plugin should be the same at the NPM package name.
|
933 |
|
934 | A plugin can require a list of other plugins to be installed prior to
|
935 | installation of the plugin. If the dependency list is not fulfilled
|
936 | the installation will fail. The idea is that you manage your plugin
|
937 | versions using NPM. If you install a plugin that is already installed
|
938 | nothing will happen.
|
939 |
|
940 | The `installInto` function receives an instance of unexpected and uses
|
941 | uses the `addAssertion` method to add new custom assertions instance.
|
942 |
|
943 | ```js
|
944 | expect.installPlugin(require('unexpected-sinon'));
|
945 | ```
|
946 |
|
947 | See the
|
948 | [unexpected-sinon](https://github.com/sunesimonsen/unexpected-sinon) or
|
949 | [unexpected-knockout](https://github.com/sunesimonsen/unexpected-knockout)
|
950 | plugins as examples of how to create a plugin.
|
951 |
|
952 | ### expect.toString()
|
953 |
|
954 | Prints all registered assertions to the console.
|
955 |
|
956 | ```js
|
957 | console.log(expect.toString());
|
958 | ```
|
959 |
|
960 | ## Using Unexpected with a test framework
|
961 |
|
962 | For example, if you create a test suite with
|
963 | [mocha](http://github.com/visionmedia/mocha).
|
964 |
|
965 | Let's say we wanted to test the following program:
|
966 |
|
967 | **math.js**
|
968 |
|
969 | ```js
|
970 | function add (a, b) { return a + b; };
|
971 | ```
|
972 |
|
973 | Our test file would look like this:
|
974 |
|
975 | ```js
|
976 | describe('math.js', function () {
|
977 | describe('add', function () {
|
978 | it('is a function', function () {
|
979 | expect(add, 'to be a', 'function');
|
980 | });
|
981 |
|
982 | it('does addition on numbers', function () {
|
983 | expect(add(1, 3), 'to be', 4);
|
984 | });
|
985 | });
|
986 | });
|
987 | ```
|
988 |
|
989 | If a certain expectation fails, an exception will be raised which gets captured
|
990 | and shown/processed by the test runner.
|
991 |
|
992 | ## Development
|
993 |
|
994 | [Everything you need to know to contribute to unexpected.](Development.md)
|
995 |
|
996 | ## License
|
997 |
|
998 | MIT, see the `LICENSE` file for details
|
999 |
|
1000 | ### Credits
|
1001 |
|
1002 | Heavily borrows from [expect.js](https://github.com/LearnBoost/expect.js) by
|
1003 | Guillermo Rauch - MIT.
|