Chai assertions for iterable objects
npm install chai-iterator[![Version][version-badge]][npm]
[![Build][build-badge]][travis]
[![Coverage][coverage-badge]][coveralls]
[![Dependencies][dependencies-badge]][greenkeeper]
- Overview
- Compatibility
- Installation
- Setup
- Expect/Should API
- Assert API
- License
chai-iterator extends the [Chai][chai] assertion library with methods for
testing [iterable][iterable] objects. Introduced in the
[ES2015 specification][ecma-iterable], iterable objects have an
[@@iterator][iterator-method] method, which allows us to iterate over them with
a [for...of][for-of] loop. A number of [built-in][built-in-iterable] types are
iterable by default, while [custom iterable objects][custom-iterable] may also
be defined. chai-iterator makes it easy to test all such objects.
In many cases the [array spread operator][array-spread] is the best way to test iterables.
chai-iterator is however very useful for testing part of a very long (or infinite) iterable.
Here is a fairly exhaustive sample of the assertions we can make using Chai
Iterator. While we could just as easily use expect or assert, we'll use
Chai's should() [assertion style][assertion-style], just to be different.
``js
[2, 3, 5].should.be.iterable;
[2, 3, 5].should.iterate.over([2, 3, 5]);
[2, 3, 5].should.iterate.from([2, 3]);
[2, 3, 5].should.iterate.until([3, 5]);
[2, 3, 5].should.iterate.for.lengthOf(3);
[2, 3, 5].should.iterate.for.length.above(2);
[2, 3, 5].should.iterate.for.length.below(4);
[2, 3, 5].should.iterate.for.length.of.at.least(3);
[2, 3, 5].should.iterate.for.length.of.at.most(3);
[2, 3, 5].should.iterate.for.length.within(2, 4);
[2, 3, 5].should.not.iterate.over([1, 2, 3]);
[{n: 2}, {n: 3}].should.deep.iterate.from([{n: 2}]);
`
Let's not limit ourselves to Arrays; we can test any iterable object.
`js`
'abcde'.should.iterate.until(['c', 'd', 'e']);
And we can pass any iterable as our expected values too.
`js`
'abcde'.should.iterate.until('cde');
chai-iterator is best used to test
[user-defined iterable objects][custom-iterable], like the one constructed by
the following [class][class].
`js
class Count {
constructor(start=0, step=1) {
this.start = start;
this.step = step;
}
*[Symbol.iterator]() {
for (let n = this.start; true; n += this.step) {
yield n;
}
}
}
`
The sequence generated by Count.prototype[@@iterator]() is infinite;from()
it continues to yield values indefinitely. Still, we can safely
use the assertion with it, since it will
terminate as soon as our expected iterable is done.
`js
let tens = new Count(10, 10);
tens.should.be.iterable;
tens.should.iterate.from([10, 20, 30]);
tens.should.iterate.from([10, 20, 30, 40, 50]);
`
Just don't go trying to use over() or
until() on infinite sequences. The former will always
fail and the latter will never stop.
Let's generate the [fibonacci sequence][fibonacci-sequence]. A
[generator function][generator-function] is just a function that returns a
[Generator][generator] object — an [iterator][iterator] that is alsoGenerator
[iterable][iterable]. We can test a just as we would any other
iterable.
`js
function* fibonacci() {
for (let [x, y] = [1, 1]; true; [x, y] = [y, x + y]) {
yield x;
}
}
fibonacci().should.iterate.from([1, 1, 2, 3, 5]);
`
Be careful though. Iterators can't go back in time. Once a value has
been yielded, it is lost forever. And so the following assertions pass.
`js
let fiborator = fibonacci();
fiborator.should.iterate.from([1, 1, 2, 3, 5]);
fiborator.should.iterate.from([8, 13, 21, 34]);
`
It usually makes more sense to construct a new Generator for each assertion.
`js`
fibonacci().should.iterate.from([1, 1, 2, 3, 5]);
fibonacci().should.iterate.from([1, 1, 2, 3, 5, 8, 13]);
chai-iterator requires that [Symbol.iterator][iterator-method] be
available in the environment. In [Node][node], this means the version must be
v4.0 or greater. While the latest versions of [most browsers][browser-list] are
compatible, web-facing projects should almost certainly use a polyfill.
The [Babel polyfill][babel-polyfill] is one option for environments that do not
natively support Symbol.iterator. More minimally, we can get away with just
two sub-modules from the [core-js][core-js] library, like so.
`js`
require('core-js/es6/symbol');
require('core-js/fn/symbol/iterator');
Install chai-iterator using [npm][npm]. And be sure, of course, to install [Chai][chai-npm].
`sh`
npm install --save chai chai-iterator
chai-iterator can be imported as a [Node][node] module, an [AMD][amd]
module, or included in an HTML [
`
#### TypeScript
[TypeScript][typescript] declarations are included in the package. To use them,
ensure chai-iterator is installed with [npm][npm], then install the declarations
and their dependencies via [typings][typings]. And be sure to install the
declarations for [chai][chai-typings].
`sh`
typings install --save-dev npm~chai npm:chai-iterator
In the [compiler options][compiler-options], set "target" to "es6", or atlib.es6.d.ts
least include a reference to [][es6-lib]. Now the following will
just work.
`ts
import chai = require("chai");
import chaiIterator = require("chai-iterator");
chai.use(chaiIterator);
[2, 3, 5].should.iterate.over([2, 3, 5]);
`
- iterable
- iterate.over()
- iterate.from()
- iterate.until()
- iterate.for.lengthOf()
- iterate.for.length.above()
- iterate.for.length.below()
- iterate.for.length.of.at.least()
- iterate.for.length.of.at.most()
- iterate.for.length.within()
#### iterable
Asserts that the target is an iterable object, i.e., that it has an
[@@iterator][iterator-method] method.
`js`
expect([2, 3, 5]).to.be.iterable;
expect('abcdefg').to.be.iterable;
expect(12345).not.to.be.iterable;
#### iterate.over(expected)
Asserts that the target iterates over a given sequence of values. Set the
[deep][deep] flag to use deep equality to compare values.
| Param | Type | Description |
| :-------- | :------- | :------------------ |
| expected | object | An iterable object. |
`js`
expect([2, 3, 5]).to.iterate.over([2, 3, 5]);
expect('abcdefg').to.itetate.over('abcdefg');
expect([2, 3, 5]).not.to.iterate.over([2, 3]);
expect([{n: 2}, {n: 3}]).to.deep.iterate.over([{n: 2}, {n: 3}]);
#### iterate.from(expected)
Asserts that the target begins iterating over a given sequence of values. Set
the [deep][deep] flag to use deep equality to compare values.
| Param | Type | Description |
| :-------- | :------- | :------------------ |
| expected | object | An iterable object. |
`js`
expect([2, 3, 5]).to.iterate.from([2, 3]);
expect('abcdefg').to.iterate.from('abc');
expect([2, 3, 5]).not.to.iterate.from([3, 5]);
expect([{n: 2}, {n: 3}]).to.deep.iterate.from([{n: 2}]);
#### iterate.until(expected)
Asserts that the target ends iteration with a given sequence of values. Set the
[deep][deep] flag to use deep equality to compare values.
| Param | Type | Description |
| :-------- | :------- | :------------------ |
| expected | object | An iterable object. |
`js`
expect([2, 3, 5]).to.iterate.until([3, 5]);
expect('abcdefg').to.iterate.until('efg');
expect([2, 3, 5]).not.to.iterate.until([2, 3]);
expect([{n: 2}, {n: 3}]).to.deep.iterate.until([{n: 3}]);
#### iterate.for.lengthOf(n)
Asserts that the target yields exactly n values.
| Param | Type | Description |
| :----- | :------- | :------------------ |
| n | number | A positive integer |
`js`
expect([2, 3, 5]).to.iterate.for.lengthOf(3);
expect('abcdefg').to.iterate.for.lengthOf(7);
expect([2, 3, 5]).not.to.iterate.for.lengthOf(7);
#### iterate.for.length.above(n)
Asserts that the target yields more than n values.
| Param | Type | Description |
| :----- | :------- | :------------------ |
| n | number | A positive integer |
`js`
expect([2, 3, 5]).to.iterate.for.length.above(2);
expect('abcdefg').to.iterate.for.length.above(5);
expect([2, 3, 5]).not.to.iterate.for.length.above(3);
#### iterate.for.length.below(n)
Asserts that the target yields fewer than n values.
| Param | Type | Description |
| :----- | :------- | :------------------ |
| n | number | A positive integer |
`js`
expect([2, 3, 5]).to.iterate.for.length.below(4);
expect('abcdefg').to.iterate.for.length.below(10);
expect([2, 3, 5]).not.to.iterate.for.length.below(3);
#### iterate.for.length.of.at.least(n)
Asserts that the target yields at least n values.
| Param | Type | Description |
| :----- | :------- | :------------------ |
| n | number | A positive integer |
`js`
expect([2, 3, 5]).to.iterate.for.length.of.at.least(2);
expect([2, 3, 5]).to.iterate.for.length.of.at.least(3);
expect([2, 3, 5]).not.to.iterate.for.length.of.at.least(4);
#### iterate.for.length.of.at.most(n)
Asserts that the target yields at most n values.
| Param | Type | Description |
| :----- | :------- | :------------------ |
| n | number | A positive integer |
`js`
expect([2, 3, 5]).to.iterate.for.length.of.at.most(4);
expect([2, 3, 5]).to.iterate.for.length.of.at.most(3);
expect([2, 3, 5]).not.to.iterate.for.length.of.at.most(2);
#### iterate.for.length.within(min, max)
Asserts that the target yields between min and max values, inclusive.
| Param | Type | Description |
| :----- | :------- | :------------------- |
| min | number | A positive integer |number
| max | | A positive integer |
`js`
expect([2, 3, 5]).to.iterate.for.length.within(2, 4);
expect([2, 3, 5]).to.iterate.for.length.within(3, 3);
expect([2, 3, 5]).not.to.iterate.for.length.within(4, 7);
- isIterable()
- isNotIterable()
- iteratesOver()
- doesNotIterateOver()
- deepIteratesOver()
- doesNotDeepIterateOver()
- iteratesFrom()
- doesNotIterateFrom()
- deepIteratesFrom()
- doesNotDeepIterateFrom()
- iteratesUntil()
- doesNotIterateUntil()
- deepIteratesUntil()
- doesNotDeepIterateUntil()
- lengthOf()
#### Parameters
The parameters for the assert methods are as follows.
| Param | Type | Description |
| :------- | :------- | :--------------------------------------- |
| value | any | Any value. |object
| expected | | An iterable object. |number
| n | | A positive integer. |string
| message? | | An optional message to display on error. |
#### isIterable(value, [message])
Asserts that a value is an iterable object, i.e., that it is an object with
an [@@iterator][iterator-method] method.
`js`
assert.isIterable([2, 3, 5]);
assert.isIterable('abcdefg');
#### isNotIterable(value, [message])
Asserts that a value is not an iterable object, i.e., that it lacks an
[@@iterator][iterator-method] method.
`js`
assert.isNotIterable(235);
assert.isNotIterable(true);
#### iteratesOver(value, expected, [message])
Asserts that a value iterates exactly over a given sequence of values.
`js`
assert.iteratesOver([2, 3, 5], [2, 3, 5]);
assert.iteratesOver('abcdefg', 'abcdefg');
#### doesNotIterateOver(value, expected, [message])
Asserts that a value does not iterate exactly over a given sequence of values.
`js`
assert.doesNotIterateOver([2, 3, 5], [1, 2, 3]);
assert.doesNotIterateOver('abcdefg', 'abc');
#### deepIteratesOver(value, expected, [message])
Asserts that a value iterates exactly over a given sequence of values, using
deep equality.
`js`
assert.deepIteratesOver([{n: 2}, {n: 3}], [{n: 2}, {n: 3}]);
assert.deepIteratesOver([[0, 2], [1, 3]], [[0, 2], [1, 3]]);
#### doesNotDeepIterateOver(value, expected, [message])
Asserts that a value does not iterate exactly over a given sequence of values,
using deep equality.
`js`
assert.doesNotDeepIterateOver([{n: 2}, {n: 3}], [{n: 5}, {n: 7}]);
assert.doesNotDeepIterateOver([[0, 2], [1, 3]], [[1, 3], [0, 2]]);
#### iteratesFrom(value, expected, [message])
Asserts that a value begins iteration with a given sequence of values.
`js`
assert.iteratesFrom([2, 3, 5], [2, 3, 5]);
assert.iteratesFrom([2, 3, 5], [2, 3]);
assert.iteratesFrom('abcdefg', 'abc');
assert.iteratesFrom('abcdefg', '');
#### doesNotIterateFrom(value, expected, [message])
Asserts that a value does not begin iteration with a given sequence of values.
`js`
assert.doesNotIterateFrom([2, 3, 5], [3, 5]);
assert.doesNotIterateFrom('abcdefg', 'cdef');
#### deepIteratesFrom(value, expected, [message])
Asserts that a value begins iteration with a given sequence of values, using
deep equality.
`js`
assert.deepIteratesFrom([{n: 2}, {n: 3}], [{n: 2}]);
assert.deepIteratesFrom([[0, 2], [1, 3]], [[0, 2]]);
#### doesNotDeepIterateFrom(value, expected, [message])
Asserts that a value does not begin iteration with a given sequence of values,
using deep equality.
`js`
assert.doesNotDeepIterateFrom([{n: 2}, {n: 3}], [{n: 5}]);
assert.doesNotDeepIterateFrom([[0, 2], [1, 3]], [[1, 3]]);
#### iteratesUntil(value, expected, [message])
Asserts that a value ends iteration with a given sequence of values.
`js`
assert.iteratesUntil([2, 3, 5], [2, 3, 5]);
assert.iteratesUntil([2, 3, 5], [3, 5]);
assert.iteratesUntil('abcdefg', 'efg');
assert.iteratesUntil('abcdefg', '');
#### doesNotIterateUntil(value, expected, [message])
Asserts that a value does not end iteration with a given sequence of values.
`js`
assert.doesNotIterateUntil([2, 3, 5], [2, 3]);
assert.doesNotIterateUntil('abcdefg', 'cdef');
#### deepIteratesUntil(value, expected, [message])
Asserts that a value ends iteration with a given sequence of values, using
deep equality.
`js`
assert.deepIteratesUntil([{n: 2}, {n: 3}], [{n: 3}]);
assert.deepIteratesUntil([[0, 2], [1, 3]], [[1, 3]]);
#### doesNotDeepIterateUntil(value, expected, [message])
Asserts that a value does not end iteration with a given sequence of values,
using deep equality.
`js`
assert.doesNotDeepIterateUntil([{n: 2}, {n: 3}], [{n: 5}]);
assert.doesNotDeepIterateUntil([[0, 2], [1, 3]], [[0, 2]]);
#### lengthOf(value, n, [message])
Asserts that an iterable yields a given number of values. If value is not an
iterable object, or if it has a 'length' property, Chai's built-inassert.lengthOf()
[][chai-assert-lengthof] will be used.
`js
function* range(min=0, max=Infinity, step=1) {
for (let n = min; n < max; n += step) {
yield n;
}
}
assert.lengthOf(range(0, 10), 10);
assert.lengthOf(range(6, 42), 36);
``
Copyright © 2016–2017 Akim McMath. Licensed under the [MIT License][license].
[version-badge]: https://badge.fury.io/js/chai-iterator.svg
[build-badge]: https://travis-ci.com/harrysarson/chai-iterator.svg?branch=master
[coverage-badge]: https://coveralls.io/repos/github/harrysarson/chai-iterator/badge.svg?branch=master
[dependencies-badge]: https://badges.greenkeeper.io/harrysarson/chai-iterator.svg
[npm]: https://www.npmjs.com/package/chai-iterator
[travis]: https://travis-ci.com/harrysarson/chai-iterator
[coveralls]: https://coveralls.io/github/harrysarson/chai-iterator?branch=master
[greenkeeper]: https://greenkeeper.io
[chai]: http://chaijs.com/
[iterable]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols#iterable
[ecma-iterable]: http://www.ecma-international.org/ecma-262/6.0/#sec-iterable-interface
[iterator-method]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator
[for-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
[array-spread]: (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
[built-in-iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#Builtin_iterables
[custom-iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#User-defined_iterables
[assertion-style]: http://chaijs.com/guide/styles/
[class]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes
[fibonacci-sequence]: https://en.wikipedia.org/wiki/Fibonacci_number
[generator-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
[generator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
[iterator]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols#iterator
[node]: https://nodejs.org/
[browser-list]: https://kangax.github.io/compat-table/es6/#test-well-known_symbols_Symbol.iterator,_existence
[babel-polyfill]: https://babeljs.io/docs/usage/polyfill/
[core-js]: https://github.com/zloirock/core-js
[amd]: https://github.com/amdjs/amdjs-api/wiki/AMD
[script-tag]: https://developer.mozilla.org/en/docs/Web/HTML/Element/script
[typescript]: http://www.typescriptlang.org/
[chai-typings]: https://github.com/typed-typings/npm-chai
[typings]: https://github.com/typings/typings
[compiler-options]: https://www.typescriptlang.org/docs/handbook/compiler-options.html
[es6-lib]: https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es6.d.ts
[deep]: http://chaijs.com/api/bdd/#method_deep
[chai-npm]: https://www.npmjs.com/package/chai
[chai-assert-lengthof]: http://chaijs.com/api/assert/#method_lengthof