Sinon spy/stub/mock assertions for Bupkis.
npm install @bupkis/sinon bupkis sinon
import { use } from 'bupkis';
import sinonAssertions from '@bupkis/sinon';
import sinon from 'sinon';
const { expect } = use(sinonAssertions);
// Basic spy assertions
const spy = sinon.spy();
spy(42);
expect(spy, 'was called');
expect(spy, 'was called once');
expect(spy, 'was called with', [42]);
// Call count
spy();
spy();
expect(spy, 'was called times', 3);
// Stub return values
const stub = sinon.stub().returns(100);
stub();
expect(stub.firstCall, 'to have returned', 100);
// Call order
const first = sinon.spy();
const second = sinon.spy();
first();
second();
expect(first, 'was called before', second);
expect([first, second], 'given call order');
// Complex call specifications
const logger = sinon.spy();
logger('info', 'started');
logger('debug', 'processing');
logger('info', 'done');
expect(logger, 'to have calls satisfying', [
['info', 'started'],
['debug', 'processing'],
['info', 'done'],
]);
✏️ Aliases:
{Spy} was called {Spy} to have been called
Asserts that a spy was called at least once.
Success:
const spy = sinon.spy();
spy();
expect(spy, 'was called');
expect(spy, 'to have been called');
Failure:
const spy = sinon.spy();
expect(spy, 'was called');
// AssertionError: Expected spy to have been called, but it was never called
Negation:
const spy = sinon.spy();
expect(spy, 'not to have been called');
✏️ Aliases:
{Spy} was not called {Spy} to not have been called
Asserts that a spy was never called.
Success:
const spy = sinon.spy();
expect(spy, 'was not called');
expect(spy, 'to not have been called');
Failure:
const spy = sinon.spy();
spy();
expect(spy, 'was not called');
// AssertionError: Expected spy to not have been called, but it was called 1 time(s)
Negation:
const spy = sinon.spy();
spy();
expect(spy, 'not was not called'); // awkward but valid
✏️ Aliases:
{Spy} was called once {Spy} to have been called once
Asserts that a spy was called exactly once.
Success:
const spy = sinon.spy();
spy();
expect(spy, 'was called once');
expect(spy, 'to have been called once');
Failure:
const spy = sinon.spy();
spy();
spy();
expect(spy, 'was called once');
// AssertionError: Expected spy to have been called exactly once
Negation:
const spy = sinon.spy();
expect(spy, 'not to have been called once');
Asserts that a spy was called exactly twice.
Success:
const spy = sinon.spy();
spy();
spy();
expect(spy, 'was called twice');
Failure:
const spy = sinon.spy();
spy();
expect(spy, 'was called twice');
// AssertionError: Expected spy to have been called exactly twice
Negation:
const spy = sinon.spy();
expect(spy, 'not was called twice');
Asserts that a spy was called exactly three times.
Success:
const spy = sinon.spy();
spy();
spy();
spy();
expect(spy, 'was called thrice');
Failure:
const spy = sinon.spy();
spy();
spy();
expect(spy, 'was called thrice');
// AssertionError: Expected spy to have been called exactly three times
Negation:
const spy = sinon.spy();
expect(spy, 'not was called thrice');
Asserts that a spy was called exactly the specified number of times.
Success:
const spy = sinon.spy();
spy();
spy();
spy();
spy();
spy();
expect(spy, 'was called times', 5);
Failure:
const spy = sinon.spy();
spy();
spy();
expect(spy, 'was called times', 5);
// AssertionError: Expected spy to have been called 5 time(s)
Negation:
const spy = sinon.spy();
spy();
expect(spy, 'not was called times', 5);
✏️ Aliases:
{Spy} to have returned {Spy} returned
Asserts that a spy returned successfully (without throwing) at least once.
Success:
const spy = sinon.spy(() => 42);
spy();
expect(spy, 'to have returned');
expect(spy, 'returned');
Failure:
const spy = sinon.spy(() => {
throw new Error('boom');
});
try {
spy();
} catch {}
expect(spy, 'to have returned');
// AssertionError: Expected spy to have returned at least once
Negation:
const spy = sinon.spy(() => {
throw new Error('boom');
});
try {
spy();
} catch {}
expect(spy, 'not to have returned');
Asserts that a spy returned successfully exactly the specified number of times.
Success:
const spy = sinon.spy(() => 42);
spy();
spy();
spy();
expect(spy, 'to have returned times', 3);
Failure:
const spy = sinon.spy(() => 42);
spy();
spy();
expect(spy, 'to have returned times', 3);
// AssertionError: Expected spy to have returned 3 time(s), but it returned 2 time(s)
Negation:
const spy = sinon.spy(() => 42);
spy();
expect(spy, 'not to have returned times', 3);
Asserts that a spy returned the specified value on at least one call. Uses Sinon's spy.returned() method for comparison (deep equality via samsam).
Success:
const spy = sinon.spy((x) => x * 2);
spy(5);
spy(10);
expect(spy, 'to have returned with', 10);
expect(spy, 'to have returned with', 20);
Failure:
const spy = sinon.spy((x) => x * 2);
spy(5);
expect(spy, 'to have returned with', 100);
// AssertionError: Expected spy to have returned specified value
Negation:
const spy = sinon.spy((x) => x * 2);
spy(5);
expect(spy, 'not to have returned with', 100);
✏️ Aliases:
{Spy} was called with {array} {Spy} to have been called with {array}
Asserts that at least one call to the spy included the specified arguments. Uses prefix matching: the spy may have been called with additional arguments beyond those specified.
Success:
const spy = sinon.spy();
spy('foo', 42, 'extra');
expect(spy, 'was called with', ['foo', 42]); // prefix match - 'extra' ignored
expect(spy, 'to have been called with', ['foo', 42, 'extra']); // exact match
Failure:
const spy = sinon.spy();
spy('bar');
expect(spy, 'was called with', ['foo']);
// AssertionError: Expected spy to have been called with specified arguments
Negation:
const spy = sinon.spy();
spy('bar');
expect(spy, 'not to have been called with', ['foo']);
Asserts that all calls to the spy included the specified arguments (prefix match).
Success:
const spy = sinon.spy();
spy('foo', 1);
spy('foo', 2);
spy('foo', 3);
expect(spy, 'was always called with', ['foo']);
Failure:
const spy = sinon.spy();
spy('foo');
spy('bar');
expect(spy, 'was always called with', ['foo']);
// AssertionError: Expected spy to always have been called with specified arguments
Negation:
const spy = sinon.spy();
spy('foo');
spy('bar');
expect(spy, 'not was always called with', ['foo']);
Asserts that at least one call to the spy had exactly the specified arguments (no additional arguments).
Success:
const spy = sinon.spy();
spy('foo', 42);
expect(spy, 'was called with exactly', ['foo', 42]);
Failure:
const spy = sinon.spy();
spy('foo', 42, 'extra');
expect(spy, 'was called with exactly', ['foo', 42]);
// AssertionError: Expected spy to have been called with exactly the specified arguments
Negation:
const spy = sinon.spy();
spy('foo', 42, 'extra');
expect(spy, 'not was called with exactly', ['foo', 42]);
Asserts that the spy was never called with the specified arguments.
Success:
const spy = sinon.spy();
spy('foo');
spy('bar');
expect(spy, 'was never called with', ['baz']);
Failure:
const spy = sinon.spy();
spy('foo');
expect(spy, 'was never called with', ['foo']);
// AssertionError: Expected spy to never have been called with specified arguments
Negation:
const spy = sinon.spy();
spy('foo');
expect(spy, 'not was never called with', ['foo']);
✏️ Aliases:
{Spy} was called on {unknown} {Spy} to have been called on {unknown}
Asserts that at least one call to the spy used the specified this context.
Success:
const obj = { name: 'test' };
const spy = sinon.spy();
spy.call(obj);
expect(spy, 'was called on', obj);
expect(spy, 'to have been called on', obj);
Failure:
const obj1 = { name: 'one' };
const obj2 = { name: 'two' };
const spy = sinon.spy();
spy.call(obj1);
expect(spy, 'was called on', obj2);
// AssertionError: Expected spy to have been called with specified this context
Negation:
const obj1 = { name: 'one' };
const obj2 = { name: 'two' };
const spy = sinon.spy();
spy.call(obj1);
expect(spy, 'not to have been called on', obj2);
Asserts that all calls to the spy used the specified this context.
Success:
const obj = { name: 'test' };
const spy = sinon.spy();
spy.call(obj);
spy.call(obj);
expect(spy, 'was always called on', obj);
Failure:
const obj1 = { name: 'one' };
const obj2 = { name: 'two' };
const spy = sinon.spy();
spy.call(obj1);
spy.call(obj2);
expect(spy, 'was always called on', obj1);
// AssertionError: Expected spy to always have been called with specified this context
Negation:
const obj1 = { name: 'one' };
const obj2 = { name: 'two' };
const spy = sinon.spy();
spy.call(obj1);
spy.call(obj2);
expect(spy, 'not was always called on', obj1);
✏️ Aliases:
{Spy} threw {Spy} to have thrown
Asserts that the spy threw an exception on at least one call.
Success:
const spy = sinon.spy(() => {
throw new Error('boom');
});
try {
spy();
} catch {}
expect(spy, 'threw');
expect(spy, 'to have thrown');
Failure:
const spy = sinon.spy();
spy();
expect(spy, 'threw');
// AssertionError: Expected spy to have thrown an exception
Negation:
const spy = sinon.spy();
spy();
expect(spy, 'not to have thrown');
Asserts that the spy threw a specific error. The parameter can be an Error instance or a string representing the error type name.
Success:
const spy = sinon.spy(() => {
throw new TypeError('bad type');
});
try {
spy();
} catch {}
expect(spy, 'threw', 'TypeError'); // match by type name
expect(spy, 'threw', new TypeError('bad type')); // match by instance
Failure:
const spy = sinon.spy(() => {
throw new Error('boom');
});
try {
spy();
} catch {}
expect(spy, 'threw', 'TypeError');
// AssertionError: Expected spy to have thrown specified exception
Negation:
const spy = sinon.spy(() => {
throw new Error('boom');
});
try {
spy();
} catch {}
expect(spy, 'not threw', 'TypeError');
Asserts that the spy threw an exception on every call.
Success:
const spy = sinon.spy(() => {
throw new Error('boom');
});
try {
spy();
} catch {}
try {
spy();
} catch {}
expect(spy, 'always threw');
Failure:
let shouldThrow = true;
const spy = sinon.spy(() => {
if (shouldThrow) {
shouldThrow = false;
throw new Error('boom');
}
});
try {
spy();
} catch {}
spy();
expect(spy, 'always threw');
// AssertionError: Expected spy to always have thrown an exception
Negation:
const spy = sinon.spy();
spy();
expect(spy, 'not always threw');
Asserts that the first spy was called before the second spy.
Success:
const first = sinon.spy();
const second = sinon.spy();
first();
second();
expect(first, 'was called before', second);
Failure:
const first = sinon.spy();
const second = sinon.spy();
second();
first();
expect(first, 'was called before', second);
// AssertionError: Expected first spy to have been called before second spy
Negation:
const first = sinon.spy();
const second = sinon.spy();
second();
first();
expect(first, 'not was called before', second);
Asserts that the first spy was called after the second spy.
Success:
const first = sinon.spy();
const second = sinon.spy();
second();
first();
expect(first, 'was called after', second);
Failure:
const first = sinon.spy();
const second = sinon.spy();
first();
second();
expect(first, 'was called after', second);
// AssertionError: Expected first spy to have been called after second spy
Negation:
const first = sinon.spy();
const second = sinon.spy();
first();
second();
expect(first, 'not was called after', second);
Asserts that a specific spy call had exactly the specified arguments.
Access individual calls via spy.firstCall, spy.secondCall, spy.thirdCall, spy.lastCall, or spy.getCall(n).
Success:
const spy = sinon.spy();
spy('foo', 42);
expect(spy.firstCall, 'to have args', ['foo', 42]);
Failure:
const spy = sinon.spy();
spy('foo', 42);
expect(spy.firstCall, 'to have args', ['bar', 42]);
// AssertionError: Expected spy call to have specified arguments
Negation:
const spy = sinon.spy();
spy('foo', 42);
expect(spy.firstCall, 'not to have args', ['bar', 42]);
Asserts that a specific spy call returned the specified value.
Success:
const stub = sinon.stub().returns(100);
stub();
expect(stub.firstCall, 'to have returned', 100);
Failure:
const stub = sinon.stub().returns(100);
stub();
expect(stub.firstCall, 'to have returned', 200);
// AssertionError: Expected spy call to have returned specified value
Negation:
const stub = sinon.stub().returns(100);
stub();
expect(stub.firstCall, 'not to have returned', 200);
Asserts that a specific spy call threw an exception.
Success:
const spy = sinon.spy(() => {
throw new Error('boom');
});
try {
spy();
} catch {}
expect(spy.firstCall, 'to have thrown');
Failure:
const spy = sinon.spy();
spy();
expect(spy.firstCall, 'to have thrown');
// AssertionError: Expected spy call to have thrown an exception
Negation:
const spy = sinon.spy();
spy();
expect(spy.firstCall, 'not to have thrown');
Asserts that a specific spy call used the specified this context.
Success:
const obj = { name: 'test' };
const spy = sinon.spy();
spy.call(obj);
expect(spy.firstCall, 'to have this', obj);
Failure:
const obj1 = { name: 'one' };
const obj2 = { name: 'two' };
const spy = sinon.spy();
spy.call(obj1);
expect(spy.firstCall, 'to have this', obj2);
// AssertionError: Expected spy call to have specified this context
Negation:
const obj1 = { name: 'one' };
const obj2 = { name: 'two' };
const spy = sinon.spy();
spy.call(obj1);
expect(spy.firstCall, 'not to have this', obj2);
Asserts that an array of spies were called in the specified order.
Success:
const first = sinon.spy();
const second = sinon.spy();
const third = sinon.spy();
first();
second();
third();
expect([first, second, third], 'given call order');
Failure:
const first = sinon.spy();
const second = sinon.spy();
const third = sinon.spy();
third();
first();
second();
expect([first, second, third], 'given call order');
// AssertionError: Expected spies to have been called in order, but spy 0 was not called before spy 1
Negation:
const first = sinon.spy();
const second = sinon.spy();
const third = sinon.spy();
third();
first();
second();
expect([first, second, third], 'not given call order');
Asserts that all calls to a spy match a specification array. Each element in the array corresponds to one call and can be either:
args, returned, threw, thisValue properties{ args: [...] })The number of specifications must match the number of calls exactly.
Success:
const spy = sinon.spy();
spy('a', 1);
spy('b', 2);
spy('c', 3);
// Using object specifications
expect(spy, 'to have calls satisfying', [
{ args: ['a', 1] },
{ args: ['b', 2] },
{ args: ['c', 3] },
]);
// Using array shorthand
expect(spy, 'to have calls satisfying', [
['a', 1],
['b', 2],
['c', 3],
]);
With return values and this context:
const obj = { multiplier: 2 };
const stub = sinon.stub().callsFake(function (x) {
return x * this.multiplier;
});
stub.call(obj, 5);
stub.call(obj, 10);
expect(stub, 'to have calls satisfying', [
{ args: [5], returned: 10, thisValue: obj },
{ args: [10], returned: 20, thisValue: obj },
]);
Failure:
const spy = sinon.spy();
spy('a');
spy('b');
expect(spy, 'to have calls satisfying', [['a'], ['c']]);
// AssertionError: Call 1: argument 0 did not match
const spy2 = sinon.spy();
spy2('a');
expect(spy2, 'to have calls satisfying', [['a'], ['b']]);
// AssertionError: Expected spy to have 2 call(s), but it had 1
Negation:
const spy = sinon.spy();
spy('a');
spy('b');
expect(spy, 'not to have calls satisfying', [['x'], ['y']]);
Copyright © 2026 Christopher "boneskull" Hiller. Licensed under BlueOak-1.0.0.