UNPKG

28.9 kBMarkdownView Raw
1# Unexpected
2
3Extensible BDD assertion toolkit inspired by
4[expect.js](https://github.com/LearnBoost/expect.js)
5
6```js
7expect(window.r, 'to be', undefined);
8expect({ a: 'b' }, 'to equal', { a: 'b' });
9expect(5, 'to be a', 'number');
10expect([], 'to be an', 'array');
11expect(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
37Install it with NPM or add it to your `package.json`:
38
39```
40$ npm install unexpected
41```
42
43Then:
44
45```js
46var expect = require('unexpected');
47```
48
49### Browser
50
51Include `unexpected.js`.
52
53```html
54<script src="unexpected.js"></script>
55```
56
57this will expose the expect function under the following namespace:
58
59```js
60var expect = weknowhow.expect;
61```
62
63### RequireJS
64
65Include the library with RequireJS the following way:
66
67```js
68require.config({
69 paths: {
70 unexpected: 'path/to/unexpected'
71 }
72});
73
74define(['unexpected'], function (expect) {
75 // Your code
76});
77```
78
79## API
80
81### to be ok
82
83asserts that the value is _truthy_
84
85**ok** / **truthy** / **falsy**: asserts that the value is _truthy_ or not
86
87```js
88expect(1, 'to be ok');
89expect(true, 'to be ok');
90expect(true, 'not to be falsy');
91expect({}, 'to be truthy');
92expect(0, 'not to be ok');
93expect(0, 'to be falsy');
94expect(null, 'to be falsy');
95expect(undefined, 'to be falsy');
96```
97
98**be**: asserts `===` equality
99
100```js
101expect(obj, 'to be', obj);
102expect(obj, 'not to be', {});
103expect(1, 'to be', 1);
104expect(1, 'not to be', true);
105expect('1', 'not to be', 1);
106expect(null, 'not to be', undefined);
107expect(null, 'to be null');
108expect(0, 'not to be null');
109expect(undefined, 'not to be null');
110expect(true, 'to be true');
111expect(false, 'not to be true');
112expect(false, 'to be false');
113expect(true, 'not to be false');
114expect(undefined, 'to be undefined');
115expect(null, 'to be defined');
116expect(false, 'to be defined');
117expect({}, 'to be defined');
118```
119
120**equal**: asserts deep equality that works with objects
121
122```js
123expect({ a: 'b' }, 'to equal', { a: 'b' });
124expect(1, 'not to equal', '1');
125expect({ one: 1 }, 'not to equal', { one: '1' });
126expect(null, 'not to equal', '1');
127var now = new Date();
128expect(now, 'to equal', now);
129expect(now, 'to equal', new Date(now.getTime()));
130expect({ 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
136expect({ a: 123, b: 456 }, 'to be canonical');
137expect([456, { a: 123 }], 'to be canonical');
138```
139
140**a** / **an**: asserts `typeof` with support for `array` type and `instanceof`
141
142```js
143expect(5, 'to be a', 'number');
144expect(5, 'to be a number');
145
146expect('abc', 'to be a', 'string');
147expect('abc', 'to be a string');
148expect('', 'to be an empty string');
149expect('abc', 'to be a non-empty string');
150
151expect([], 'to be an', 'array');
152expect([], 'to be an array');
153expect([], 'to be an', Array);
154expect([], 'to be an empty array');
155expect([123], 'to be a non-empty array');
156
157expect({foo: 123}, 'to be an', 'object');
158expect({foo: 123}, 'to be an object');
159expect({foo: 123}, 'to be a non-empty object');
160expect({}, 'to be an empty object');
161
162expect(null, 'not to be an', 'object');
163expect(null, 'not to be an object');
164
165expect(true, 'to be a', 'boolean');
166expect(true, 'to be a boolean');
167
168expect(expect, 'to be a', 'function');
169expect(expect, 'to be a function');
170```
171
172**NaN**: asserts that the value is `NaN`
173
174```js
175expect(NaN, 'to be NaN');
176expect(2, 'not to be NaN');
177```
178
179**close to**: asserts that the difference between two numbers is <= epsilon
180
181```js
182expect(1.5, 'to be close to', 1.500001, 1e-5);
183expect(1.5, 'not to be close to', 1.499, 1e-4);
184```
185
186epsilon defaults to 1e-9 if omitted:
187
188```js
189expect(1.5, 'to be close to', 1.5000000001);
190```
191
192**match**: asserts `String` regular expression match
193
194```js
195expect('test', 'to match', /.*st/);
196expect('test', 'not to match', /foo/);
197expect(null, 'not to match', /foo/);
198```
199
200**contain**: asserts indexOf for an array or string
201
202```js
203expect([1, 2], 'to contain', 1);
204expect('hello world', 'to contain', 'world');
205```
206
207**length**: asserts array `.length`
208
209```js
210expect([], 'to have length', 0);
211expect([1,2,3], 'to have length', 3);
212expect([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
218expect([], 'to be empty');
219expect('', 'to be empty');
220expect({ my: 'object' }, 'not to be empty');
221expect([1,2,3], 'not to be empty');
222expect({ length: 0, duck: 'typing' }, 'to be empty');
223```
224
225**property**: asserts presence of an own property (and value optionally)
226
227```js
228expect([1, 2], 'to have property', 'length');
229expect([1, 2], 'to have property', 'length', 2);
230expect({a: 'b'}, 'to have property', 'a');
231expect({a: 'b'}, 'to have property', 'a', 'b');
232expect({a: 'b'}, 'to have property', 'toString');
233expect({a: 'b'}, 'to have own property', 'a');
234expect(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
240expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have properties', ['a', 'b']);
241expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have own properties', ['a', 'b']);
242expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'not to have properties', ['k', 'l']);
243expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have properties', {
244 a: 'a',
245 b: { c: 'c' }
246});
247expect([ '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
256expect({ a: 'b' }, 'to have key', 'a');
257expect({ a: 'b' }, 'not to have key', 'b');
258expect({ a: 'b', c: 'd' }, 'to not only have key', 'a');
259expect({ a: 'b', c: 'd' }, 'to only have keys', 'a', 'c');
260expect({ a: 'b', c: 'd' }, 'to only have keys', ['a', 'c']);
261expect({ 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
267expect(fn, 'to throw exception');
268expect(fn, 'to throw');
269expect(fn, 'to throw exception', function (e) { // get the exception object
270 expect(e, 'to be a', SyntaxError);
271});
272expect(fn, 'to throw exception', /matches the exception message/);
273expect(fn, 'to throw error', 'matches the exact exception message');
274expect(fn2, 'not to throw error');
275```
276
277**arity**: asserts that the `Function` takes the given number of arguments
278
279```js
280expect(Math.max, 'to have arity', 2);
281```
282
283**finite/infinite**: asserts a finite or infinite number
284
285```js
286expect(123, 'to be finite');
287expect(Infinity, 'not to be finite');
288expect(Infinity, 'to be infinite');
289```
290
291**within**: asserts a number within a range
292
293```js
294expect(0, 'to be within', 0, 4);
295expect(1, 'to be within', 0, 4);
296expect(4, 'to be within', 0, 4);
297expect(-1, 'not to be within', 0, 4);
298expect(5, 'not to be within', 0, 4);
299```
300
301**greater than** / **above**: asserts `>`
302
303```js
304expect(3, 'to be greater than', 2);
305expect(1, 'to be above', 0);
306expect(4, 'to be >', 3);
307expect(4, '>', 3);
308```
309
310**greater than or equal to**: asserts `>`
311
312```js
313expect(3, 'to be greater than or equal to', 2);
314expect(3, 'to be >=', 3);
315expect(3, '>=', 3);
316```
317
318**less than** / **below**: asserts `<`
319
320```js
321expect(0, 'to be less than', 4);
322expect(0, 'to be below', 1);
323expect(3, 'to be <', 4);
324expect(3, '<', 4);
325```
326
327**less than or equal to**: asserts `>`
328
329```js
330expect(0, 'to be less than or equal to', 4);
331expect(4, 'to be <=', 4);
332expect(3, '<=', 4);
333```
334
335**positive**: assert that a number is positive
336
337```js
338expect(3, 'to be positive');
339```
340
341**negative**: assert that a number is negative
342
343```js
344expect(-1, 'to be negative');
345```
346
347**fail**: explicitly forces failure.
348
349```js
350expect.fail()
351expect.fail('Custom failure message')
352expect.fail('{0} was expected to be {1}', 0, 'zero');
353```
354
355I case you want to rethrow an error, you should always use
356`expect.fail`, as it ensures that the error message will be correct
357for the different error modes.
358
359```js
360var error = new Error('throw me');
361expect.fail(new Error(error));
362```
363
364When 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
367output can be written to.
368
369```js
370expect.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
384expect([0, 1, 2, 3, 4], 'to be an array whose items satisfy', function (item, index) {
385 expect(item, 'to be a number');
386});
387
388expect([0, 1, 2, 3, 4], 'to be an array whose items satisfy', 'to be a number');
389
390expect([[1], [2]], 'to be an array whose items satisfy',
391 'to be an array whose items satisfy', 'to be a number');
392
393expect([[], []], 'to be a non-empty array whose items satisfy', function (item) {
394 expect(item, 'to be an empty array');
395});
396```
397
398Using this assertion result in very detailed error reporting as shown in the below example:
399
400```js
401expect([[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
409will output:
410
411```
412failed 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
421All properties and nested objects mentioned in the right-hand side object are
422required to be present. Primitive values are compared with `to equal` semantics:
423
424```js
425expect({ hey: { there: true } }, 'to satisfy', { hey: {} });
426```
427
428To disallow additional properties in the subject, use `to exhaustively satisfy`:
429
430```js
431expect({ hey: { there: true } }, 'to exhaustively satisfy', { hey: { there: true } });
432```
433
434Regular expressions and functions in the right-hand side object will be run
435against the corresponding values in the subject:
436
437```js
438expect({ bar: 'quux', baz: true }, 'to satisfy', { bar: /QU*X/i });
439```
440
441Can be combined with `expect.it` to create complex specifications that delegate to
442existing assertions:
443
444```js
445expect({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
459expect({ 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
464expect({ foo: 0, bar: 1, baz: 2, qux: 3 },
465 'to be a map whose keys satisfy',
466 'to match', /^[a-z]{3}$/);
467```
468
469Using this assertion result in very detailed error reporting as shown in the below example:
470
471```js
472expect({ 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
478will output:
479
480```
481failed 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
488expect({ 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
493expect({ foo: 0, bar: 1, baz: 2, qux: 3 },
494 'to be a map whose values satisfy',
495 'to be a number');
496```
497
498Using this assertion result in very detailed error reporting as shown in the below example:
499
500```js
501expect({ 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
509will output:
510
511```
512failed 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
528Before extending the `expect` instance with new assertions it is
529usually a good idea to clone it, so you don't change the global
530instance. You do that by calling the `clone` method on `expect`.
531Adding new assertions to the clone will not affect the original
532instance.
533
534### expect.addAssertion([typeName, ...], [pattern, ...], handler)
535
536Signature:
537
538```js
539expect.addAssertion(pattern, handler);
540expect.addAssertion([pattern, ...]], handler);
541expect.addAssertion(typeName, pattern, handler);
542expect.addAssertion(typeName, [pattern, ...], handler);
543expect.addAssertion([typeName, ...], pattern, handler);
544expect.addAssertion([typeName, ...], [pattern, ...], handler);
545```
546
547New assertions can be added to Unexpected to following way.
548
549```js
550expect.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
556The above assertion definition makes the following expects possible:
557
558```js
559expect([1,2,3], 'to be sorted');
560expect([1,2,3], 'to be ordered');
561expect([2,1,3], 'not to be sorted');
562expect([2,1,3], 'not to be ordered');
563expect([3,2,1], 'to be sorted', function (x, y) { return y - x; });
564```
565
566Let's dissect the different parts of the custom assertion we just
567introduced.
568
569The first parameter to `addAssertion` is a string or an array
570specifying which types the assertion should be defined on. In this
571case the assertion in only defined for arrays. In case the type is not
572specified the assertion will be defined for the type `any`, and would
573be applicable any type. See the `Extending Unexpected with new types`
574section for more information about the type system in Unexpected.
575
576The second parameter to `addAssertion` is a string or an array stating
577the patterns this assertion should match. A pattern has the following
578syntax. A word in square brackets represents a flag that can either be
579there or not. If the flag is present `this.flags[flag]` will contain
580the value `true`. In this case `not` is a flag. When a flag it present
581in a nested `expect` it will be inserted if the flag is present;
582otherwise it will be removed. Text that is in parentheses with
583vertical bars between them are treated as alternative texts that can
584be used. In this case you can write _ordered_ as an alternative to
585_sorted_.
586
587The last parameter to `addAssertion` is function that will be called
588when `expect` is invoked with an expectation matching the type and
589pattern of the assertion.
590
591So in this case, when `expect` is called the following way:
592
593```js
594expect([3,2,1], 'to be sorted', reverse);
595```
596
597The handler to our assertion will be called with the values the
598following way, where the _not_ flag in the nested expect will be
599removed:
600
601```js
602expect.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
609When a call to `expect` fails inside your assertion the standard error
610message 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```
614expected [ 4, 3, 1, 2 ] to be sorted
615```
616
617We can control the output of the nested expects by using the `this.errorMode`
618flag.
619
620```js
621expect.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
628This will change the error output to:
629
630```
631expected [ 4, 3, 1, 2 ] to equal [ 1, 2, 3, 4 ]
632```
633
634If we change the error mode to _nested_, we get the following:
635
636```
637expected [ 4, 3, 1, 2 ] to be sorted
638 expected [ 4, 3, 1, 2 ] to equal [ 1, 2, 3, 4 ]
639```
640
641The best resource for learning more about custom assertions is to look
642at 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
648Unexpected comes with a type system that is used to explain how
649different types are compared, diffed, inspected and is also used to
650limit the scope of assertions.
651
652The 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
659Unexpected can be extended with knowledge about new types by calling
660the `addType` method with a type definition. The type definition must
661implement the required parts of the following interface:
662
663Required 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
669Optional 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
684Adding new types to the system is best explained by an example. Let's
685say we wanted to add first class support for a `Person` type:
686
687```js
688function Person(name, age) {
689 this.name = name;
690 this.age = age;
691}
692```
693
694We start out by creating a basic type for handling `Person`
695instances. The name of the type should be `Person` and it should
696inherit from the build in `object` type. Furthermore we add an
697`identify` method that will recognize `Person` instances.
698
699```js
700expect.addType({
701 name: 'Person',
702 base: 'object',
703 identify: function (value) {
704 return value instanceof Person;
705 }
706});
707```
708
709When you specify a base type, you inherit the optional members you
710didn't implement. In this case we inherited the methods `equal`,
711`inspect` and `diff` from the `object` type.
712
713Imagine that we make a failing expectation on a person instance:
714
715```js
716expect(new Person('John Doe', 42), 'to equal', new Person('Jane Doe', 24));
717```
718
719the result is the following output:
720
721<img src="./images/addType-basic.png" alt="Failed expectation of basic type">
722
723That is already quite helpful, but the output misses the information
724that it is `Person` instances we are comparing. We can fix that by
725implementing an `inspect` method on the type.
726
727```js
728expect.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
744Now we get the following output:
745
746<img src="./images/addType-inspect.png" alt="Failed expectation of type with inspect defined">
747
748That is a bit better, let me explain how it works. The `inspect`
749method is called with the value to be inspected, the depth this type
750should be inspected with, an output the inspected value should be
751written to and an inspect function that can be used to recursively
752inspect members. The output is an instance of
753[magicpen](https://github.com/sunesimonsen/magicpen) extended with a
754number of [styles](https://github.com/sunesimonsen/unexpected/blob/master/lib/styles.js).
755
756We write `new Person(` without styling, then we append the inspected
757`name`, write a `, `, inspect the `age` and finish with the closing
758parenthesis. When `inspect` is called without a depth parameter it
759defaults to `depth-1`. Values inspected with depth zero will be
760inspected as `...`. In this case we always want the name so we forward the
761same depth to the `inspect` function.
762
763Let's say we wanted persons only to be compared by name and not by
764age. Then we need to override the `equal` method:
765
766```js
767expect.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
786This will produce the same output as above, but that means the diff if
787wrong. It states that the age should be changed. We can fix that the
788following way:
789
790```js
791expect.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
815The above `diff` method just calls the `diff` method on the base type
816with objects that only contain the name. The `object` diff will take
817care of all the hard work. We could also have called the `diff`
818function we got as an argument, but that will go off detecting the
819types of the parameters, therefore it is faster to call `diff` method
820on the base directly when you know it is the one you need.
821
822You could also do something really custom as seen below:
823
824```js
825expect.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
874That would produce the following output.
875
876<img src="./images/addType-improved-diff.png" alt="Failed expectation of type with an improved diff">
877
878This is a rather complicated example and I wont go though the details,
879but I would like to comment on the `inline` flag. When we diff objects
880against each other, the values of the keys will be diffed against each
881other. That means diffs are inserted into the containing
882structure. You can control this behavior using the `inline` flag. If
883the child diff is inline, it means that it will be appended directly
884into the parent; otherwise the diff will be inserted in an annotation
885block. 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
892Now that we have implemented a type, we can start adding assertions to
893it. These assertions will only work on this type or types inheriting
894from the type.
895
896```js
897expect.addAssertion('Person', 'to be above legal age', function (expect, subject) {
898 expect(subject.age, 'to be greater than or equal to', 18);
899});
900
901expect(new Person('Jane Doe', 24), 'to be above legal age');
902```
903
904Because `Person` inherits from `object` you can use all assertion
905defined for `object` or any of it's ancestors. Here is an example:
906
907```js
908expect(new Person('Jane Doe', 24), 'to have keys', 'name', 'age');
909expect(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
915The best resource for learning more about custom types is to look at
916how 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
922Unexpected 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
932The name of the plugin should be the same at the NPM package name.
933
934A plugin can require a list of other plugins to be installed prior to
935installation of the plugin. If the dependency list is not fulfilled
936the installation will fail. The idea is that you manage your plugin
937versions using NPM. If you install a plugin that is already installed
938nothing will happen.
939
940The `installInto` function receives an instance of unexpected and uses
941uses the `addAssertion` method to add new custom assertions instance.
942
943```js
944expect.installPlugin(require('unexpected-sinon'));
945```
946
947See the
948[unexpected-sinon](https://github.com/sunesimonsen/unexpected-sinon) or
949[unexpected-knockout](https://github.com/sunesimonsen/unexpected-knockout)
950plugins as examples of how to create a plugin.
951
952### expect.toString()
953
954Prints all registered assertions to the console.
955
956```js
957console.log(expect.toString());
958```
959
960## Using Unexpected with a test framework
961
962For example, if you create a test suite with
963[mocha](http://github.com/visionmedia/mocha).
964
965Let's say we wanted to test the following program:
966
967**math.js**
968
969```js
970function add (a, b) { return a + b; };
971```
972
973Our test file would look like this:
974
975```js
976describe('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
989If a certain expectation fails, an exception will be raised which gets captured
990and 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
998MIT, see the `LICENSE` file for details
999
1000### Credits
1001
1002Heavily borrows from [expect.js](https://github.com/LearnBoost/expect.js) by
1003Guillermo Rauch - MIT.