chai-iterator extends the Chai assertion library with methods for
testing iterable objects. Introduced in the
ES2015 specification, iterable objects have an
@@iterator
method, which allows us to iterate over them with
a for...of
loop. A number of built-in types are
iterable by default, while custom iterable objects may also
be defined. chai-iterator makes it easy to test all such objects.
In many cases the array spread operator 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, just to be different.
[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.
'abcde'.should.iterate.until(['c', 'd', 'e']);
And we can pass any iterable as our expected values too.
'abcde'.should.iterate.until('cde');
chai-iterator is best used to test user-defined iterable objects, like the one constructed by the following class.
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;
it continues to yield values indefinitely. Still, we can safely
use the from()
assertion with it, since it will
terminate as soon as our expected iterable is done.
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. A
generator function is just a function that returns a
Generator
object — an iterator that is also
iterable. We can test a Generator
just as we would any other
iterable.
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.
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.
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
be
available in the environment. In Node, this means the version must be
v4.0 or greater. While the latest versions of most browsers are
compatible, web-facing projects should almost certainly use a polyfill.
The 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 library, like so.
require('core-js/es6/symbol');
require('core-js/fn/symbol/iterator');
Install chai-iterator using npm. And be sure, of course, to install Chai.
npm install --save chai chai-iterator
chai-iterator can be imported as a Node module, an AMD
module, or included in an HTML <script>
tag. For
TypeScript users, declarations are installed with the package.
To set up chai-iterator for Node, make sure the version is v4.0 or
higher, as prior versions lack support for the @@iterator
method.
const chai = require('chai');
const chaiIterator = require('chai-iterator');
chai.use(chaiIterator);
chai-iterator can be set up inside of an AMD module like so.
define((require, exports, module) => {
let chai = require('chai');
let chaiIterator = require('chai-iterator');
chai.use(chaiIterator);
});
chai-iterator can be included via a <script>
tag. If it is
loaded after chai.js
, Chai will use it automatically.
<script src="chai.js"></script>
<script src="chai-iterator.js"></script>
TypeScript declarations are included in the package. To use them, ensure chai-iterator is installed with npm, then install the declarations and their dependencies via typings. And be sure to install the declarations for chai.
typings install --save-dev npm~chai npm:chai-iterator
In the compiler options, set "target"
to "es6"
, or at
least include a reference to lib.es6.d.ts
. Now the following will
just work.
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()
Asserts that the target is an iterable object, i.e., that it has an
@@iterator
method.
expect([2, 3, 5]).to.be.iterable;
expect('abcdefg').to.be.iterable;
expect(12345).not.to.be.iterable;
Asserts that the target iterates over a given sequence of values. Set the
deep
flag to use deep equality to compare values.
Param | Type | Description |
---|---|---|
expected | object |
An iterable object. |
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}]);
Asserts that the target begins iterating over a given sequence of values. Set
the deep
flag to use deep equality to compare values.
Param | Type | Description |
---|---|---|
expected | object |
An iterable object. |
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}]);
Asserts that the target ends iteration with a given sequence of values. Set the
deep
flag to use deep equality to compare values.
Param | Type | Description |
---|---|---|
expected | object |
An iterable object. |
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}]);
Asserts that the target yields exactly n values.
Param | Type | Description |
---|---|---|
n | number |
A positive integer |
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);
Asserts that the target yields more than n values.
Param | Type | Description |
---|---|---|
n | number |
A positive integer |
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);
Asserts that the target yields fewer than n values.
Param | Type | Description |
---|---|---|
n | number |
A positive integer |
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);
Asserts that the target yields at least n values.
Param | Type | Description |
---|---|---|
n | number |
A positive integer |
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);
Asserts that the target yields at most n values.
Param | Type | Description |
---|---|---|
n | number |
A positive integer |
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);
Asserts that the target yields between min and max values, inclusive.
Param | Type | Description |
---|---|---|
min | number |
A positive integer |
max | number |
A positive integer |
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()
The parameters for the assert methods are as follows.
Param | Type | Description |
---|---|---|
value | any |
Any value. |
expected | object |
An iterable object. |
n | number |
A positive integer. |
message? | string |
An optional message to display on error. |
Asserts that a value is an iterable object, i.e., that it is an object with
an @@iterator
method.
assert.isIterable([2, 3, 5]);
assert.isIterable('abcdefg');
Asserts that a value is not an iterable object, i.e., that it lacks an
@@iterator
method.
assert.isNotIterable(235);
assert.isNotIterable(true);
Asserts that a value iterates exactly over a given sequence of values.
assert.iteratesOver([2, 3, 5], [2, 3, 5]);
assert.iteratesOver('abcdefg', 'abcdefg');
Asserts that a value does not iterate exactly over a given sequence of values.
assert.doesNotIterateOver([2, 3, 5], [1, 2, 3]);
assert.doesNotIterateOver('abcdefg', 'abc');
Asserts that a value iterates exactly over a given sequence of values, using deep equality.
assert.deepIteratesOver([{n: 2}, {n: 3}], [{n: 2}, {n: 3}]);
assert.deepIteratesOver([[0, 2], [1, 3]], [[0, 2], [1, 3]]);
Asserts that a value does not iterate exactly over a given sequence of values, using deep equality.
assert.doesNotDeepIterateOver([{n: 2}, {n: 3}], [{n: 5}, {n: 7}]);
assert.doesNotDeepIterateOver([[0, 2], [1, 3]], [[1, 3], [0, 2]]);
Asserts that a value begins iteration with a given sequence of values.
assert.iteratesFrom([2, 3, 5], [2, 3, 5]);
assert.iteratesFrom([2, 3, 5], [2, 3]);
assert.iteratesFrom('abcdefg', 'abc');
assert.iteratesFrom('abcdefg', '');
Asserts that a value does not begin iteration with a given sequence of values.
assert.doesNotIterateFrom([2, 3, 5], [3, 5]);
assert.doesNotIterateFrom('abcdefg', 'cdef');
Asserts that a value begins iteration with a given sequence of values, using deep equality.
assert.deepIteratesFrom([{n: 2}, {n: 3}], [{n: 2}]);
assert.deepIteratesFrom([[0, 2], [1, 3]], [[0, 2]]);
Asserts that a value does not begin iteration with a given sequence of values, using deep equality.
assert.doesNotDeepIterateFrom([{n: 2}, {n: 3}], [{n: 5}]);
assert.doesNotDeepIterateFrom([[0, 2], [1, 3]], [[1, 3]]);
Asserts that a value ends iteration with a given sequence of values.
assert.iteratesUntil([2, 3, 5], [2, 3, 5]);
assert.iteratesUntil([2, 3, 5], [3, 5]);
assert.iteratesUntil('abcdefg', 'efg');
assert.iteratesUntil('abcdefg', '');
Asserts that a value does not end iteration with a given sequence of values.
assert.doesNotIterateUntil([2, 3, 5], [2, 3]);
assert.doesNotIterateUntil('abcdefg', 'cdef');
Asserts that a value ends iteration with a given sequence of values, using deep equality.
assert.deepIteratesUntil([{n: 2}, {n: 3}], [{n: 3}]);
assert.deepIteratesUntil([[0, 2], [1, 3]], [[1, 3]]);
Asserts that a value does not end iteration with a given sequence of values, using deep equality.
assert.doesNotDeepIterateUntil([{n: 2}, {n: 3}], [{n: 5}]);
assert.doesNotDeepIterateUntil([[0, 2], [1, 3]], [[0, 2]]);
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-in
assert.lengthOf()
will be used.
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].