Skip to content

Commit

Permalink
feat(spy): adds support for spy.nth call assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
Carlos David Nexans authored and stalniy committed Sep 5, 2017
1 parent a2f6a80 commit 4b1661c
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 42 deletions.
31 changes: 31 additions & 0 deletions README.md
Expand Up @@ -175,6 +175,37 @@ expect(spy).to.have.been.called.always.with.exactly('foo');
spy.should.have.been.called.always.with.exactly('foo');
```

#### .nth(n).called.with

Asserts that the nth call of the spy has been made with the list of arguments provided. This assertion comes with other three flavors:

* .first.called.with
* .second.called.with
* .third.called.with

```js
spy('foo');
spy('bar');
spy('baz');
spy('foobar');
expect(spy).to.have.been.first.called.with('foo');
spy.should.have.been.first.called.with('foo');
expect(spy).on.nth(5).be.called.with('foobar');
spy.should.on.nth(5).be.called.with('foobar');
```

These assertions requires the spy to be called at least the
number of times required, for example

```js
spy('foo');
spy('bar');
expect(spy).to.have.been.third.called.with('baz');
spy.should.have.been.third.called.with('baz');
```

Won't pass because the spy has not been called a third time.

#### .once

Assert that a spy has been called exactly once.
Expand Down
124 changes: 103 additions & 21 deletions chai-spies.js
Expand Up @@ -48,7 +48,7 @@ var spy = function (chai, _) {
* const array = []
* const spy = chai.spy.sandbox();
* const [push, pop] = spy.on(array, ['push', 'pop']);
*
*
* spy.on(array, 'push', returns => 1)
*
* @param {Object} object
Expand Down Expand Up @@ -226,7 +226,7 @@ var spy = function (chai, _) {
s += " }";
return s;
};

proxy.__spy = {
calls: []
, called: false
Expand Down Expand Up @@ -439,41 +439,111 @@ var spy = function (chai, _) {
});

/**
* ### .with
* # nth call (spy, n, arguments)
*
* Asserts that the nth call of the spy has been called with
*
*/

function assertWith () {
new Assertion(this._obj).to.be.spy;
var expArgs = [].slice.call(arguments, 0)
, calls = this._obj.__spy.calls
, always = _.flag(this, 'spy always')
function nthCallWith(spy, n, expArgs) {
if (spy.calls.length < n) return false;

var actArgs = spy.calls[n].slice()
, passed = 0;

calls.forEach(function (call) {
var actArgs = call.slice()
, found = 0;

expArgs.forEach(function (expArg) {
for (var i = 0; i < actArgs.length; i++) {
if (_.eql(actArgs[i], expArg)) {
found++;
actArgs.splice(i, 1);
break;
}
expArgs.forEach(function (expArg) {
for (var i = 0; i < actArgs.length; i++) {
if (_.eql(actArgs[i], expArg)) {
passed++;
actArgs.splice(i, 1);
break;
}
});
if (found === expArgs.length) passed++;
}
});

return passed === expArgs.length
}

function numberOfCallsWith(spy, expArgs) {
var found = 0
, calls = spy.calls;

for (var i = 0; i < calls.length; i++) {
if (nthCallWith(spy, i, expArgs)) {
found++;
}
}

return found;
}

Assertion.addProperty('first', function () {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', 1);
}
});

Assertion.addProperty('second', function () {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', 2);
}
});

Assertion.addProperty('third', function () {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', 3);
}
});

Assertion.addProperty('on');

Assertion.addChainableMethod('nth', function (n) {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', n);
}
});

function generateOrdinalNumber(n) {
if (n === 1) return 'first';
if (n === 2) return 'second';
if (n === 3) return 'third';
return n + 'th';
}

/**
* ### .with
*
*/

function assertWith() {
new Assertion(this._obj).to.be.spy;
var expArgs = [].slice.call(arguments, 0)
, spy = this._obj.__spy
, calls = spy.calls
, always = _.flag(this, 'spy always')
, nthCall = _.flag(this, 'spy nth call with');

if (always) {
var passed = numberOfCallsWith(spy, expArgs);
this.assert(
passed === calls.length
, 'expected ' + this._obj + ' to have been always called with #{exp} but got ' + passed + ' out of ' + calls.length
, 'expected ' + this._obj + ' to have not always been called with #{exp}'
, expArgs
);
} else if (nthCall) {
var ordinalNumber = generateOrdinalNumber(nthCall),
actArgs = calls[nthCall - 1];
new Assertion(this._obj).to.be.have.been.called.min(nthCall);
this.assert(
nthCallWith(spy, nthCall - 1, expArgs)
, 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with #{exp} but got #{act}'
, 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with #{exp}'
, expArgs
, actArgs
);
} else {
var passed = numberOfCallsWith(spy, expArgs);
this.assert(
passed > 0
, 'expected ' + this._obj + ' to have been called with #{exp}'
Expand Down Expand Up @@ -512,6 +582,7 @@ var spy = function (chai, _) {
, _with = _.flag(this, 'spy with')
, args = [].slice.call(arguments, 0)
, calls = this._obj.__spy.calls
, nthCall = _.flag(this, 'spy nth call with')
, passed;

if (always && _with) {
Expand All @@ -527,6 +598,17 @@ var spy = function (chai, _) {
, 'expected ' + this._obj + ' to have not always been called with exactly #{exp}'
, args
);
} else if(_with && nthCall) {
var ordinalNumber = generateOrdinalNumber(nthCall),
actArgs = calls[nthCall - 1];
new Assertion(this._obj).to.be.have.been.called.min(nthCall);
this.assert(
_.eql(actArgs, args)
, 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with exactly #{exp} but got #{act}'
, 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with exactly #{exp}'
, args
, actArgs
);
} else if (_with) {
passed = 0;
calls.forEach(function (call) {
Expand Down
124 changes: 103 additions & 21 deletions lib/spy.js
Expand Up @@ -42,7 +42,7 @@ module.exports = function (chai, _) {
* const array = []
* const spy = chai.spy.sandbox();
* const [push, pop] = spy.on(array, ['push', 'pop']);
*
*
* spy.on(array, 'push', returns => 1)
*
* @param {Object} object
Expand Down Expand Up @@ -220,7 +220,7 @@ module.exports = function (chai, _) {
s += " }";
return s;
};

proxy.__spy = {
calls: []
, called: false
Expand Down Expand Up @@ -433,41 +433,111 @@ module.exports = function (chai, _) {
});

/**
* ### .with
* # nth call (spy, n, arguments)
*
* Asserts that the nth call of the spy has been called with
*
*/

function assertWith () {
new Assertion(this._obj).to.be.spy;
var expArgs = [].slice.call(arguments, 0)
, calls = this._obj.__spy.calls
, always = _.flag(this, 'spy always')
function nthCallWith(spy, n, expArgs) {
if (spy.calls.length < n) return false;

var actArgs = spy.calls[n].slice()
, passed = 0;

calls.forEach(function (call) {
var actArgs = call.slice()
, found = 0;

expArgs.forEach(function (expArg) {
for (var i = 0; i < actArgs.length; i++) {
if (_.eql(actArgs[i], expArg)) {
found++;
actArgs.splice(i, 1);
break;
}
expArgs.forEach(function (expArg) {
for (var i = 0; i < actArgs.length; i++) {
if (_.eql(actArgs[i], expArg)) {
passed++;
actArgs.splice(i, 1);
break;
}
});
if (found === expArgs.length) passed++;
}
});

return passed === expArgs.length
}

function numberOfCallsWith(spy, expArgs) {
var found = 0
, calls = spy.calls;

for (var i = 0; i < calls.length; i++) {
if (nthCallWith(spy, i, expArgs)) {
found++;
}
}

return found;
}

Assertion.addProperty('first', function () {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', 1);
}
});

Assertion.addProperty('second', function () {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', 2);
}
});

Assertion.addProperty('third', function () {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', 3);
}
});

Assertion.addProperty('on');

Assertion.addChainableMethod('nth', function (n) {
if ('undefined' !== this._obj.__spy) {
_.flag(this, 'spy nth call with', n);
}
});

function generateOrdinalNumber(n) {
if (n === 1) return 'first';
if (n === 2) return 'second';
if (n === 3) return 'third';
return n + 'th';
}

/**
* ### .with
*
*/

function assertWith() {
new Assertion(this._obj).to.be.spy;
var expArgs = [].slice.call(arguments, 0)
, spy = this._obj.__spy
, calls = spy.calls
, always = _.flag(this, 'spy always')
, nthCall = _.flag(this, 'spy nth call with');

if (always) {
var passed = numberOfCallsWith(spy, expArgs);
this.assert(
passed === calls.length
, 'expected ' + this._obj + ' to have been always called with #{exp} but got ' + passed + ' out of ' + calls.length
, 'expected ' + this._obj + ' to have not always been called with #{exp}'
, expArgs
);
} else if (nthCall) {
var ordinalNumber = generateOrdinalNumber(nthCall),
actArgs = calls[nthCall - 1];
new Assertion(this._obj).to.be.have.been.called.min(nthCall);
this.assert(
nthCallWith(spy, nthCall - 1, expArgs)
, 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with #{exp} but got #{act}'
, 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with #{exp}'
, expArgs
, actArgs
);
} else {
var passed = numberOfCallsWith(spy, expArgs);
this.assert(
passed > 0
, 'expected ' + this._obj + ' to have been called with #{exp}'
Expand Down Expand Up @@ -506,6 +576,7 @@ module.exports = function (chai, _) {
, _with = _.flag(this, 'spy with')
, args = [].slice.call(arguments, 0)
, calls = this._obj.__spy.calls
, nthCall = _.flag(this, 'spy nth call with')
, passed;

if (always && _with) {
Expand All @@ -521,6 +592,17 @@ module.exports = function (chai, _) {
, 'expected ' + this._obj + ' to have not always been called with exactly #{exp}'
, args
);
} else if(_with && nthCall) {
var ordinalNumber = generateOrdinalNumber(nthCall),
actArgs = calls[nthCall - 1];
new Assertion(this._obj).to.be.have.been.called.min(nthCall);
this.assert(
_.eql(actArgs, args)
, 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with exactly #{exp} but got #{act}'
, 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with exactly #{exp}'
, args
, actArgs
);
} else if (_with) {
passed = 0;
calls.forEach(function (call) {
Expand Down

0 comments on commit 4b1661c

Please sign in to comment.