Skip to content

Commit

Permalink
Implement 'to have no calls satisfying' assertion.
Browse files Browse the repository at this point in the history
  • Loading branch information
papandreou committed Jan 12, 2017
1 parent 280e701 commit f5b393f
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
83 changes: 83 additions & 0 deletions documentation/assertions/spy/to-have-no-calls-satisfying.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
Passes if none of the calls to a spy [satisfy](http://unexpected.js.org/assertions/any/to-satisfy/) a given spec:

```js
var increment = sinon.spy(function increment(n) {
return n + 1;
});
increment(42);
increment(46);
expect(increment, 'to have no calls satisfying', { args: [ 43 ], returnValue: 44 });
```

In case of a failing expectation you get the following output:

```js
var quux = sinon.spy().named('quux');
quux(789);
quux(123, 456);

expect(quux, 'to have no calls satisfying', { args: [ 123, 456 ] });
```

```output
expected quux to have no calls satisfying { args: [ 123, 456 ] }
quux( 789 ); at theFunction (theFileName:xx:yy)
quux( 123, 456 ); at theFunction (theFileName:xx:yy) // should be removed
```

An array value will be interpreted as a shorthand for `{ args: ... }`:

```js
expect(quux, 'to have no calls satisfying', [ 123 ]);
```

Likewise for an object with only numerical properties:

```js
expect(quux, 'to have no calls satisfying', { 1: 789 });
```

Note that the individual parameters are matched with
[`to satisfy`](http://unexpected.js.org/assertions/any/to-satisfy/)
semantics, so the following fails despite the actual call
also had a `bar` property in the object passed as the first
parameter:

```js
var mySpy = sinon.spy().named('mySpy');
mySpy({foo: 123, bar: 456});
expect(mySpy, 'to have no calls satisfying', { args: [ { foo: 123 } ] });
```

```output
expected mySpy to have no calls satisfying { args: [ { foo: 123 } ] }
mySpy( { foo: 123, bar: 456 } ); at theFunction (theFileName:xx:yy) // should be removed
```

If that's not what you want, consider using the `exhaustively` flag. Then
the assertion passes because none of the calls matched exhaustively:

```js
expect(mySpy, 'to have no calls exhaustively satisfying', { args: [ { foo: 123 } ] });
```

You can also specify the expected call as a function that performs it:

```js
var foo = sinon.spy().named('foo');
foo(1);
foo(2);

expect(foo, 'to have no calls satisfying', function () {
foo(1);
});
```

```output
expected foo to have no calls satisfying foo( 1 );
foo( 1 ); at theFunction (theFileName:xx:yy) // should be removed
foo( 2 ); at theFunction (theFileName:xx:yy)
```
57 changes: 57 additions & 0 deletions lib/unexpected-sinon.js
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,63 @@
}
});

expect.addAssertion('<spy|array|sinonSandbox> to have no calls [exhaustively] satisfying <object>', function (expect, subject, value) {
var keys = Object.keys(value);
if (
keys.length > 0 &&
keys.every(function (key) {
return /^[1-9]*[0-9]+$/.test(key);
})
) {
return expect(subject, 'to have no calls [exhaustively] satisfying', { args: value });
}

expect.errorMode = 'defaultOrNested';

// Get a flattened array of all calls to all the specified spies:
var calls = Array.prototype.concat.apply([], extractSpies(subject).map(getCalls));
var promises = calls.map(function (call) {
return expect.promise(function () {
return expect(call, 'to [exhaustively] satisfy', value);
});
});
return expect.promise.settle(promises).then(function () {
var failed = promises.some(function (promise) {
return promise.isFulfilled();
});

if (failed) {
return expect(calls, 'to equal', toSpyCalls(calls.filter(function (call, i) {
return promises[i].isRejected();
})));
}
});
});

expect.addAssertion('<spy|array|sinonSandbox> to have no calls [exhaustively] satisfying <array>', function (expect, subject, value) {
return expect(subject, 'to have no calls [exhaustively] satisfying', { args: value });
});

expect.addAssertion('<spy|array|sinonSandbox> to have no calls satisfying <function>', function (expect, subject, value) {
var expectedSpyCallSpecs = recordSpyCalls(extractSpies(subject), value);
var expectedSpyCalls = [];
expectedSpyCallSpecs.forEach(function (expectedSpyCallSpec) {
expectedSpyCalls.push(expectedSpyCallSpec.call);
delete expectedSpyCallSpec.call;
delete expectedSpyCallSpec.callId;
});
if (expectedSpyCalls.length > 0) {
expect.argsOutput[0] = function (output) {
output.appendInspected(expectedSpyCalls);
};
}
if (expectedSpyCallSpecs.length !== 1) {
expect.errorMode = 'nested';
expect.fail('expected the provided function to call the spy exactly once, but it called it ' + expectedSpyCallSpecs.length + ' times');
}
return expect(subject, 'to have no calls satisfying', expectedSpyCallSpecs[0]);
});

expect.addAssertion('<spy|array|sinonSandbox> to have a call [exhaustively] satisfying <object>', function (expect, subject, value) {
var keys = Object.keys(value);
if (
Expand Down
31 changes: 31 additions & 0 deletions test/unexpected-sinon.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,37 @@ describe('unexpected-sinon', function () {
});
});

describe('to have no calls satisfying', function () {
it('passes if the spy was never called with the provided arguments', function () {
spy('foo', 'true');
expect(spy, 'to have no calls satisfying', ['bar', expect.it('to be truthy')]);
});

it('fails if the spy was called with the provided arguments', function () {
expect(function () {
spy('bar', 'true');
expect(spy, 'to have no calls satisfying', ['bar', expect.it('to be truthy')]);
}, 'to throw exception',
"expected spy1 to have no calls satisfying [ 'bar', expect.it('to be truthy') ]\n" +
"\n" +
"spy1( 'bar', 'true' ); at theFunction (theFileName:xx:yy) // should be removed"
);
});

it('fails if the spy has a call that satisfies the criteria and another call that does not', function () {
expect(function () {
spy('foo');
spy('bar', {});
expect(spy, 'to have no calls satisfying', { 0: 'bar' });
}, 'to throw exception',
"expected spy1 to have no calls satisfying { 0: 'bar' }\n" +
"\n" +
"spy1( 'foo' ); at theFunction (theFileName:xx:yy)\n" +
"spy1( 'bar', {} ); at theFunction (theFileName:xx:yy) // should be removed"
);
});
});

describe('was called with exactly', function () {
it('passes if the spy was called with the provided arguments and no others', function () {
spy('foo', 'bar', 'baz');
Expand Down

0 comments on commit f5b393f

Please sign in to comment.