Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement 'to have all calls satisfying' assertion #40

Merged
merged 1 commit into from Jan 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 104 additions & 0 deletions documentation/assertions/spy/to-have-all-calls-satisfying.md
@@ -0,0 +1,104 @@
Passes if all calls to a spy [satisfy](http://unexpected.js.org/assertions/any/to-satisfy/) a given spec.
Also, the spy must have been called at least once for the assertion to pass.
Compare with the [to have a call satisfying](../to-have-a-call-satisfying/),
which only requires one call to satisfying the expectation.

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

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

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

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

```output
expected quux to have all calls satisfying { args: [ 'foo', 456 ] }

quux( 'foo', 456 ); at theFunction (theFileName:xx:yy)
quux(
123, // should equal 'foo'
456
); at theFunction (theFileName:xx:yy)
```

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

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

expect(baz, 'to have all calls satisfying', [ 123, 456 ]);
```

Likewise for an object with only numerical properties:

```js
expect(baz, 'to have a call satisfying', { 1: 456 });
```

Note that the individual arguments are matched with
[`to satisfy`](http://unexpected.js.org/assertions/any/to-satisfy/)
semantics, which means that objects are allowed to have more properties than you
specify, so the following passes:

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

If that's not what you want, consider using the `exhaustively` flag:

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

```output
expected mySpy to have all calls exhaustively satisfying { args: [ { foo: 123 } ] }

mySpy(
{
foo: 123,
bar: 456 // should be removed
}
); at theFunction (theFileName:xx:yy)
mySpy(
{
foo: 123,
baz: 789 // should be removed
}
); at theFunction (theFileName:xx:yy)
```

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 all calls satisfying', function () {
foo(1);
});
```

```output
expected foo to have all calls satisfying foo( 1 );

foo( 1 ); at theFunction (theFileName:xx:yy)
foo(
2 // should equal 1
); at theFunction (theFileName:xx:yy)
```
42 changes: 42 additions & 0 deletions lib/unexpected-sinon.js
Expand Up @@ -612,6 +612,48 @@
return expect(subject, 'to have a call satisfying', expectedSpyCallSpecs[0]);
});

expect.addAssertion('<spy|array|sinonSandbox> to have all 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 all 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));
return expect(calls, 'to have items [exhaustively] satisfying', value);
});

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

expect.addAssertion('<spy|array|sinonSandbox> to have all 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 all calls satisfying', expectedSpyCallSpecs[0]);
});

expect.addAssertion('<spy> was [always] called with [exactly] <any*>', function (expect, subject) {
expect.errorMode = 'defaultOrNested';
var args;
Expand Down
220 changes: 220 additions & 0 deletions test/unexpected-sinon.spec.js
Expand Up @@ -929,6 +929,226 @@ describe('unexpected-sinon', function () {
});
});

describe('to have all calls satisfying', function () {
describe('when passed a spec object', function () {
it('should succeed when a spy call satisfies the spec', function () {
spy(123, 456);
expect(spy, 'to have all calls satisfying', {
args: [ 123, 456 ]
});
});

it('should fail when the spy was not called at all', function () {
expect(function () {
expect(spy, 'to have all calls satisfying', {
args: [ 123, 456 ]
});
}, 'to throw',
"expected spy1 to have all calls satisfying { args: [ 123, 456 ] }\n" +
" expected [] to have items satisfying { args: [ 123, 456 ] }\n" +
" expected [] to be non-empty"
);
});

it('should fail when one of the calls had the wrong arguments', function () {
spy(456);
spy(567);
expect(function () {
expect(spy, 'to have all calls satisfying', {
args: [ 456 ]
});
}, 'to throw',
"expected spy1 to have all calls satisfying { args: [ 456 ] }\n" +
"\n" +
"spy1( 456 ); at theFunction (theFileName:xx:yy)\n" +
"spy1(\n" +
" 567 // should equal 456\n" +
"); at theFunction (theFileName:xx:yy)"
);
});

describe('with the exhaustively flag', function () {
it('should succeed when a spy call satisfies the spec', function () {
spy(123, { foo: 'bar' });
expect(spy, 'to have all calls satisfying', {
args: [ 123, { foo: 'bar' } ]
});
});

it('should fail when a spy call does not satisfy the spec only because of the "exhaustively" semantics', function () {
spy(123, { foo: 'bar', quux: 'baz' });
expect(function () {
expect(spy, 'to have all calls exhaustively satisfying', {
args: [ 123, { foo: 'bar' } ]
});
}, 'to throw',
"expected spy1 to have all calls exhaustively satisfying { args: [ 123, { foo: 'bar' } ] }\n" +
"\n" +
"spy1(\n" +
" 123,\n" +
" {\n" +
" foo: 'bar',\n" +
" quux: 'baz' // should be removed\n" +
" }\n" +
"); at theFunction (theFileName:xx:yy)"
);
});
});
});

describe('when passed an array (shorthand for {args: ...})', function () {
it('should succeed', function () {
spy(123, { foo: 'bar' });
expect(spy, 'to have all calls satisfying', [ 123, { foo: 'bar' } ]);
});

it('should fail with a diff', function () {
expect(function () {
spy(123, { foo: 'bar' });
expect(spy, 'to have all calls satisfying', [ 123, { foo: 'baz' } ]);
}, 'to throw',
"expected spy1 to have all calls satisfying [ 123, { foo: 'baz' } ]\n" +
"\n" +
"spy1(\n" +
" 123,\n" +
" {\n" +
" foo: 'bar' // should equal 'baz'\n" +
" //\n" +
" // -bar\n" +
" // +baz\n" +
" }\n" +
"); at theFunction (theFileName:xx:yy)"
);
});
});

describe('when passed an array with only numerical properties (shorthand for {args: ...})', function () {
it('should succeed', function () {
spy(123, { foo: 'bar' });
expect(spy, 'to have all calls satisfying', {0: 123, 1: {foo: 'bar'}});
});

it('should fail with a diff', function () {
expect(function () {
spy(123, { foo: 'bar' });
expect(spy, 'to have all calls satisfying', {0: 123, 1: {foo: 'baz'}});
}, 'to throw',
"expected spy1 to have all calls satisfying { 0: 123, 1: { foo: 'baz' } }\n" +
"\n" +
"spy1(\n" +
" 123,\n" +
" {\n" +
" foo: 'bar' // should equal 'baz'\n" +
" //\n" +
" // -bar\n" +
" // +baz\n" +
" }\n" +
"); at theFunction (theFileName:xx:yy)"
);
});
});

describe('when passed a function that performs the expected call', function () {
it('should succeed when a spy call satisfies the spec', function () {
spy(123, 456);
expect(spy, 'to have all calls satisfying', function () {
spy(123, 456);
});
});

it('should fail if the function does not call the spy', function () {
expect(function () {
expect(spy, 'to have all calls satisfying', function () {});
}, 'to throw',
"expected spy1 to have all calls satisfying function () {}\n" +
" expected the provided function to call the spy exactly once, but it called it 0 times"
);
});

it('should fail if the function calls the spy more than once', function () {
expect(function () {
expect(spy, 'to have all calls satisfying', function () {
spy(123);
spy(456);
});
}, 'to throw',
"expected spy1 to have all calls satisfying\n" +
"spy1( 123 );\n" +
"spy1( 456 );\n" +
" expected the provided function to call the spy exactly once, but it called it 2 times"
);
});

it('should fail when the spy was called, but one of the calls had the wrong arguments', function () {
spy(123);
spy(456);
expect(function () {
expect(spy, 'to have all calls satisfying', function () {
spy(123);
});
}, 'to throw',
"expected spy1 to have all calls satisfying spy1( 123 );\n" +
"\n" +
"spy1( 123 ); at theFunction (theFileName:xx:yy)\n" +
"spy1(\n" +
" 456 // should equal 123\n" +
"); at theFunction (theFileName:xx:yy)"
);
});
});

describe('when passed a sinon sandbox as the subject', function () {
it('should succeed', function () {
var sandbox = sinon.sandbox.create();
var spy1 = sandbox.spy().named('spy1');
sandbox.spy().named('spy2');
spy1(123);
spy1(123);
return expect(sandbox, 'to have all calls satisfying', { spy: spy1, args: [ 123 ] });
});

it('should fail with a diff', function () {
var sandbox = sinon.sandbox.create();
var spy1 = sandbox.spy().named('spy1');
spy1(456);
return expect(function () {
return expect(sandbox, 'to have all calls satisfying', { spy: spy1, args: [ 123 ] });
}, 'to error with',
"expected sinon sandbox to have all calls satisfying { spy: spy1, args: [ 123 ] }\n" +
"\n" +
"spy1(\n" +
" 456 // should equal 123\n" +
"); at theFunction (theFileName:xx:yy)"
);
});
});

describe('when passed an array of spies as the subject', function () {
it('should succeed', function () {
var spy1 = sinon.spy().named('spy1');
var spy2 = sinon.spy().named('spy2');
spy1(123);
return expect([spy1, spy2], 'to have all calls satisfying', { spy: spy1, args: [ 123 ] });
});

it('should fail with a diff', function () {
var sandbox = sinon.sandbox.create();
var spy1 = sandbox.spy().named('spy1');
var spy2 = sandbox.spy().named('spy2');
spy1(123);
spy2(456);
return expect(function () {
return expect([spy1, spy2], 'to have all calls satisfying', { spy: spy1, args: [ 123 ] });
}, 'to error with',
"expected [ spy1, spy2 ] to have all calls satisfying { spy: spy1, args: [ 123 ] }\n" +
"\n" +
"spy1( 123 ); at theFunction (theFileName:xx:yy)\n" +
"spy2( 456 ); at theFunction (theFileName:xx:yy) // should be spy1( 123 );"
);
});
});
});

describe('to have calls satisfying', function () {
it('should complain if the args value is passed as an object with non-numerical properties', function () {
spy(123);
Expand Down