UNPKG

unexpected

Version:

Minimalistic BDD assertion toolkit inspired by [expect.js](https://github.com/LearnBoost/expect.js)

635 lines (478 loc) 16.6 kB
# Unexpected Minimalistic BDD assertion toolkit inspired by [expect.js](https://github.com/LearnBoost/expect.js) ```js expect(window.r, 'to be', undefined); expect({ a: 'b' }, 'to equal', { a: 'b' }); expect(5, 'to be a', 'number'); expect([], 'to be an', 'array'); expect(window, 'not to be an', Image); ``` [![NPM version](https://badge.fury.io/js/unexpected.png)](http://badge.fury.io/js/unexpected) [![Build Status](https://travis-ci.org/sunesimonsen/unexpected.svg?branch=master)](https://travis-ci.org/sunesimonsen/unexpected) [![Coverage Status](https://coveralls.io/repos/sunesimonsen/unexpected/badge.png)](https://coveralls.io/r/sunesimonsen/unexpected) [![Dependency Status](https://david-dm.org/sunesimonsen/unexpected.png)](https://david-dm.org/sunesimonsen/unexpected) [Run the test in the browser](http://sunesimonsen.github.io/unexpected/test/tests.production.html) ## Features - Fast - Provides really nice error messages - Helps you if you misspells assertions - Compatible with all test frameworks. - Node.JS ready (`require('unexpected')`). - Single global with no prototype extensions or shims. - Cross-browser: works on IE6+, Firefox, Safari, Chrome, Opera. ## How to use ### Node Install it with NPM or add it to your `package.json`: ``` $ npm install unexpected ``` Then: ```js var expect = require('unexpected'); ``` ### Browser Include `unexpected.js`. ```html <script src="unexpected.js"></script> ``` this will expose the expect function under the following namespace: ```js var expect = weknowhow.expect; ``` ### RequireJS Include the library with RequireJS the following way: ```js require.config({ paths: { unexpected: 'path/to/unexpected' } }); define(['unexpected'], function (expect) { // Your code }); ``` ## API ### to be ok asserts that the value is _truthy_ **ok** / **truthy** / **falsy**: asserts that the value is _truthy_ or not ```js expect(1, 'to be ok'); expect(true, 'to be ok'); expect(true, 'not to be falsy'); expect({}, 'to be truthy'); expect(0, 'not to be ok'); expect(0, 'to be falsy'); expect(null, 'to be falsy'); expect(undefined, 'to be falsy'); ``` **be**: asserts `===` equality ```js expect(obj, 'to be', obj); expect(obj, 'not to be', {}); expect(1, 'to be', 1); expect(1, 'not to be', true); expect('1', 'not to be', 1); expect(null, 'not to be', undefined); expect(null, 'to be null'); expect(0, 'not to be null'); expect(undefined, 'not to be null'); expect(true, 'to be true'); expect(false, 'not to be true'); expect(false, 'to be false'); expect(true, 'not to be false'); expect(undefined, 'to be undefined'); ``` **equal**: asserts deep equality that works with objects ```js expect({ a: 'b' }, 'to equal', { a: 'b' }); expect(1, 'not to equal', '1'); expect({ one: 1 }, 'not to equal', { one: '1' }); expect(null, 'not to equal', '1'); var now = new Date(); expect(now, 'to equal', now); expect(now, 'to equal', new Date(now.getTime())); expect({ now: now }, 'to equal', { now: now }); ``` **canonical**: asserts that an object has its properties defined in sorted order at all levels ```js expect({ a: 123, b: 456 }, 'to be canonical'); expect([456, { a: 123 }], 'to be canonical'); ``` **a** / **an**: asserts `typeof` with support for `array` type and `instanceof` ```js expect(5, 'to be a', 'number'); expect(5, 'to be a number'); expect('abc', 'to be a', 'string'); expect('abc', 'to be a string'); expect('', 'to be an empty string'); expect('abc', 'to be a non-empty string'); expect([], 'to be an', 'array'); expect([], 'to be an array'); expect([], 'to be an', Array); expect([], 'to be an empty array'); expect([123], 'to be a non-empty array'); expect({foo: 123}, 'to be an', 'object'); expect({foo: 123}, 'to be an object'); expect({foo: 123}, 'to be a non-empty object'); expect({}, 'to be an empty object'); expect(null, 'not to be an', 'object'); expect(null, 'not to be an object'); expect(true, 'to be a', 'boolean'); expect(true, 'to be a boolean'); expect(expect, 'to be a', 'function'); expect(expect, 'to be a function'); ``` **NaN**: asserts that the value is `NaN` ```js expect(NaN, 'to be NaN'); expect({}, 'to be NaN'); expect(2, 'not to be NaN'); expect(null, 'not to be NaN'); expect(undefined, 'to be NaN'); expect("String", 'to be NaN'); ``` **close to**: asserts that the difference between two numbers is <= epsilon ```js expect(1.5, 'to be close to', 1.500001, 1e-5); expect(1.5, 'not to be close to', 1.499, 1e-4); ``` epsilon defaults to 1e-9 if omitted: ```js expect(1.5, 'to be close to', 1.5000000001); ``` **match**: asserts `String` regular expression match ```js expect('test', 'to match', /.*st/); expect('test', 'not to match', /foo/); expect(null, 'not to match', /foo/); ``` **contain**: asserts indexOf for an array or string ```js expect([1, 2], 'to contain', 1); expect('hello world', 'to contain', 'world'); expect(null, 'not to contain', 'world'); ``` **length**: asserts array `.length` ```js expect([], 'to have length', 0); expect([1,2,3], 'to have length', 3); expect([1,2,3], 'not to have length', 4); ``` **empty**: asserts that an array is empty or not ```js expect([], 'to be empty'); expect('', 'to be empty'); expect({}, 'to be empty'); expect({ length: 0, duck: 'typing' }, 'to be empty'); expect({ my: 'object' }, 'not to be empty'); expect([1,2,3], 'not to be empty'); ``` **property**: asserts presence of an own property (and value optionally) ```js expect([1, 2], 'to have property', 'length'); expect([1, 2], 'to have property', 'length', 2); expect({a: 'b'}, 'to have property', 'a'); expect({a: 'b'}, 'to have property', 'a', 'b'); expect({a: 'b'}, 'to have property', 'toString'); expect({a: 'b'}, 'to have own property', 'a'); expect(Object.create({a: 'b'}), 'not to have own property', 'a'); ``` **properties**: assert presence of properties in an object (and value optionally) ```js expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have properties', ['a', 'b']); expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have own properties', ['a', 'b']); expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to not have properties', ['k', 'l']); expect({ a: 'a', b: { c: 'c' }, d: 'd' }, 'to have properties', { a: 'a', b: { c: 'c' } }); expect([ 'a', { c: 'c' }, 'd' ], 'to have properties', { 1: { c: 'c' } 2: 'd', }); ``` **key** / **keys**: asserts the presence of a key. Supports the `only` modifier ```js expect(null, 'not to have key', 'a'); expect({ a: 'b' }, 'to have key', 'a'); expect({ a: 'b' }, 'not to have key', 'b'); expect({ a: 'b', c: 'd' }, 'to not only have key', 'a'); expect({ a: 'b', c: 'd' }, 'to only have keys', 'a', 'c'); expect({ a: 'b', c: 'd' }, 'to only have keys', ['a', 'c']); expect({ a: 'b', c: 'd', e: 'f' }, 'to not only have keys', ['a', 'c']); ``` **throw exception** / **throw error** / **throw**: asserts that the `Function` throws or not when called ```js expect(fn, 'to throw exception'); expect(fn, 'to throw'); expect(fn, 'to throw exception', function (e) { // get the exception object expect(e, 'to be a', SyntaxError); }); expect(fn, 'to throw exception', /matches the exception message/); expect(fn, 'to throw error', 'matches the exact exception message'); expect(fn2, 'not to throw error'); ``` **finite/infinite**: asserts a finite or infinite number ```js expect(123, 'to be finite'); expect(Infinity, 'not to be finite'); expect(Infinity, 'to be infinite'); expect(false, 'not to be infinite'); ``` **within**: asserts a number within a range ```js expect(0, 'to be within', 0, 4); expect(1, 'to be within', 0, 4); expect(4, 'to be within', 0, 4); expect(-1, 'not to be within', 0, 4); expect(5, 'not to be within', 0, 4); ``` **greater than** / **above**: asserts `>` ```js expect(3, 'to be greater than', 2); expect(1, 'to be above', 0); expect(4, 'to be >', 3); expect(4, '>', 3); ``` **greater than or equal to**: asserts `>` ```js expect(3, 'to be greater than or equal to', 2); expect(3, 'to be >=', 3); expect(3, '>=', 3); ``` **less than** / **below**: asserts `<` ```js expect(0, 'to be less than', 4); expect(0, 'to be below', 1); expect(3, 'to be <', 4); expect(3, '<', 4); ``` **less than or equal to**: asserts `>` ```js expect(0, 'to be less than or equal to', 4); expect(4, 'to be <=', 4); expect(3, '<=', 4); ``` **positive**: assert that a number is positive ```js expect(3, 'to be positive'); ``` **negative**: assert that a number is negative ```js expect(-1, 'to be negative'); ``` **fail**: explicitly forces failure. ```js expect.fail() expect.fail('Custom failure message') expect.fail('{0} was expected to be {1}', 0, 'zero'); ``` **array whose items satify**: will run an assertion function for each items in an array ```js expect([0, 1, 2, 3, 4], 'to be an array whose items satisfy', function (item, index) { expect(item, 'to be a number'); }); expect([0, 1, 2, 3, 4], 'to be an array whose items satisfy', 'to be a number'); expect([[1], [2]], 'to be an array whose items satisfy', 'to be an array whose items satisfy', 'to be a number'); expect([[], []], 'to be a non-empty array whose items satisfy', function (item) { expect(item, 'to be an empty array'); }); ``` Using this assertion result in very detailed error reporting show in the below example: ```js expect([[0, 1, 2], [4, '5', 6], [7, 8, '9']], 'to be an array whose items satisfy', function (arr) { expect(arr, 'to be an array whose items satisfy', function (item) { expect(item, 'to be a number'); }); }); ``` will output: ``` failed expectation in [ [ 0, 1, 2 ], [ 4, '5', 6 ], [ 7, 8, '9' ] ]: 1: failed expectation in [ 4, '5', 6 ]: 1: expected '5' to be a 'number' 2: failed expectation in [ 7, 8, '9' ]: 2: expected '9' to be a 'number' ``` **map whose keys satify**: will run an assertion function for each key in a map ```js expect({ foo: 0, bar: 1, baz: 2, qux: 3 }, 'to be a map whose keys satisfy', function (key) { expect(key, 'to match', /^[a-z]{3}$/); }); expect({ foo: 0, bar: 1, baz: 2, qux: 3 }, 'to be a map whose keys satisfy', 'to match', /^[a-z]{3}$/); ``` Using this assertion result in very detailed error reporting show in the below example: ```js expect({ foo: 0, bar: 1, baz: 2, qux: 3, quux: 4 }, 'to be a map whose keys satisfy', function (key) { expect(key, 'to have length', 3); }); ``` will output: ``` failed expectation on keys foo, bar, baz, qux, quux: quux: expected 'quux' to have length 3 ``` **map whose values satify**: will run an assertion function for each value in a map ```js expect({ foo: 0, bar: 1, baz: 2, qux: 3 }, 'to be a map whose values satisfy', function (value) { expect(value, 'to be a number'); }); expect({ foo: 0, bar: 1, baz: 2, qux: 3 }, 'to be a map whose values satisfy', 'to be a number'); ``` Using this assertion result in very detailed error reporting show in the below example: ```js expect({ foo: [0, 1, 2], bar: [4, '5', 6], baz: [7, 8, '9'] }, 'to be a map whose values satisfy', function (arr) { expect(arr, 'to be an array whose items satisfy', function (item) { expect(item, 'to be a number'); }); }); ``` will output: ``` failed expectation in { foo: [ 0, 1, 2 ], bar: [ 4, '5', 6 ], baz: [ 7, 8, '9' ] }: bar: failed expectation in [ 4, '5', 6 ]: 1: expected '5' to be a 'number' baz: failed expectation in [ 7, 8, '9' ]: 2: expected '9' to be a 'number' ``` ## Extending Unexpected with new assertions ### expect.clone() Before extending the `expect` instance with new assertions it is usually a good idea to clone it, so you don't change the global instance. You do that by calling the `clone` method on `expect`. Adding new assertions to the clone will not affect the original instance. ### expect.addAssertion(...assertionString, handler) Warning: if you were an early adopter and used `addAssertion` before it was made public, the API has change slightly to allow more advanced assertions. New assertions can be added to Unexpected to following way. ```js expect.addAssertion('[not] to be (sorted|ordered)', function(expect, subject, cmp) { expect(subject, '[not] to equal', [].concat(subject).sort(cmp)); }); ``` The above assertion definition makes the following expects possible: ```js expect([1,2,3], 'to be sorted'); expect([1,2,3], 'to be ordered'); expect([2,1,3], 'not to be sorted'); expect([2,1,3], 'not to be ordered'); expect([3,2,1], 'to be sorted', function (x, y) { return y - x; }); ``` Let's dissect the different parts of the custom assertion we just introduced. The first parameter to `addAssertion` is a string describing the different expectation strings this custom assertion should match. A word in square brackets represents a flag that can either be there or not. If the flag is present `this.flags[flag]` will contain the value `true`. In this case `not` is a flag. When a flag it present in a nested `expect` it will be inserted is the custom assertion has that flag; otherwise it will be removed. Text that is in parentheses with vertical bars between them are treated as alternative texts that can be used. In this case you can write _ordered_ as an alternative to _sorted_. The last parameter to `addAssertion` is function that will be called when `expect` is invoked with one of the expectation strings generated from the custom assertion. When the `expect` function is called the following way: ```js expect(testSubject, expectationString, ...arguments); ``` The expectation string is used to identify a handler for the expectation. The handler is then called with an instance of `expect` that can be used inside the custom assertion, the test subject and the rest of the arguments. So in this case when `expect` is called the following way: ```js expect([3,2,1], 'to be sorted', reverse); ``` The handler to our custom assertion will be called with the values this way, where the _not_ flag in the nested expect will be removed: ```js expect.addAssertion('[not] to be (sorted|ordered)', function(expect, [3,2,1], reverse){ expect([3,2,1], '[not] to equal', [].concat([3,2,1]).sort(reverse)); }); ``` #### Controlling the output of nested expects When an `expect` fails inside your custom assertion the standard error message for the custom assertion will be used. In the case of our _sorted_ assertion the output will be something along the lines: ``` expected [ 4, 3, 1, 2 ] to be sorted ``` We can control the output of the nested expects by using the `this.errorMode` flag. ```js expect.addAssertion('[not] to be (sorted|ordered)', function(expect, subject, cmp) { this.errorMode = 'bubble'; expect(subject, '[not] to equal', [].concat(subject).sort(cmp)); }); ``` This will change the error output to: ``` expected [ 4, 3, 1, 2 ] to equal [ 1, 2, 3, 4 ] ``` If we change the error mode to _nested_, we get the following: ``` expected [ 4, 3, 1, 2 ] to be sorted expected [ 4, 3, 1, 2 ] to equal [ 1, 2, 3, 4 ] ``` The best resource for learning more about custom assertions is to look at how the predefined assertions are build: [unexpected-assertions.js](https://github.com/sunesimonsen/unexpected/blob/master/lib/unexpected-assertions.js) ### expect.installPlugin(plugin) Unexpected plugins are just functions that uses the `addAssertion` method to add new custom assertions to the `expect` instance. ```js expect.installPlugin(require('unexpected-sinon')); ``` See the [unexpected-sinon](https://github.com/sunesimonsen/unexpected-sinon) plugin as an example on how to create a plugin. ## Print all registered assertions to the console ```js console.log(expect.toString()); ``` ## Using Unexpected with a test framework For example, if you create a test suite with [mocha](http://github.com/visionmedia/mocha). Let's say we wanted to test the following program: **math.js** ```js function add (a, b) { return a + b; }; ``` Our test file would look like this: ```js describe('test suite', function () { it('should expose a function', function () { expect(add, 'to be a', 'function'); }); it('should do math', function () { expect(add(1, 3), 'to be', 4); }); }); ``` If a certain expectation fails, an exception will be raised which gets captured and shown/processed by the test runner. ## Development [Everything you need to know to contribute to unexpected.](Development.md) ## Credits MIT, see the `LICENSE` file for details ### 3rd-party Heavily borrows from [expect.js](https://github.com/LearnBoost/expect.js) by Guillermo Rauch - MIT.