Skip to content

sinon

Created: 2016-05-23 12:26:56 -0700 Modified: 2019-11-19 12:20:00 -0800

http://sinonjs.org/

These let you check arguments, return values, how many times a function was called, whether or not it threw an exception, etc. This can be useful if you don’t have a particular assert in code and you don’t want to instrument production code (or rely on code coverage) just to see if something was called or if it had the right arguments.

function sum(x, y) {

return x + y;

}

const spy = sinon.spy(sum);

spy(5, 6);

console.log(‘spy.calledWith(5, 6): ’ + spy.calledWith(5, 6)); // true

console.log(‘spy.calledWith(5, 7): ’ + spy.calledWith(5, 7)); // false

console.log(‘spy.callCount: ’ + spy.callCount); // 1

console.log(‘spy.threw(): ’ + spy.threw()); // false

Instrumenting an object’s method should be done with the string notation, otherwise you won’t instrument the method correctly:

const spy = sinon.spy(bsf.validator, ‘validateDeletedBlueprints’); // You can’t do sinon.spy(bsf.validator.validateDeletedBlueprints)

return bsf.runTest()

.catch((error) => {

sinon.assert.threw(spy);

});

Asynchronous code / Promises with spies (i.e. checking if a Promise was rejected)

Section titled Asynchronous code / Promises with spies (i.e. checking if a Promise was rejected)

Sinon doesn’t have this by default in its spies. You can make stubs into fake Promises if you want, but not spies.

Working around this is possible by keeping track of something like a “gotError” variable, but it’s cumbersome. You should replace it with something like chai/chai-as-promised (see this note).

This is the poor workaround that you shouldn’t use:

it('should test Promise rejection', function() {
let gotError = false;
return Promise.reject(new Error('Failed'))
.catch((error) => {gotError = true;})
.finally(() => {
assert(gotError);
});
});

Part of the reason why this is bad is because if you ever chain calls where you expect one of them to fail, then you’ll probably need a spy just to know how many times the function was called successfully thanks to how “.then” and “.catch” work. If this doesn’t make sense, don’t worry, you shouldn’t be using this workaround anyway.

HiDeoo : Adam13531 FYI, there is also sinon-chai so you can do spy.should.have.been.calledThrice for example, same with expect or assert

Attempted to wrap FUNCTION_NAME which is already wrapped (reference)

Just call “spy.restore()” at the end of your function.

calledWith isn’t showing what you expect

Sinon saves its arguments by reference without cloning them, so suppose you have code like this:

// Dev code
class TimedPool {
constructor(clearPoolCallback) {
this.entries = [];
this.clearPoolCallback = clearPoolCallback;
}
addEntry(entry) {
this.entries.push(entry);
}
invokeCallback() {
this.clearPoolCallback(this.entries);
this.entries.length = 0;
}
}
// Test code
const spy = sinon.spy();
const timedPool = new TimedPool(spy);
timedPool.addEntry('hello');
timedPool.invokeCallback();
sinon.assert.called(spy);
sinon.assert.calledWith(spy, ['hello']); // this will fail

This fails because the dev code clears the array in invokeCallback, so the spy will get ”[‘hello’]” passed to it, but the array gets cleared before calledWith can be hit.

This is really just an easy way to create functions without having to fully define them. Stubs have a lot of the same functions that spies have.

Stubs can be used to force particular code-paths, e.g. error paths.

var callback = sinon.stub();

callback.withArgs(42).returns(1);

callback.withArgs(1).throws(“TypeError”);

callback(); // no return value

callback(42); // 1

callback(1); // throws error

// To make a function that always returns a particular value, just do this.

const alwaysReturnsFalse = sinon.stub().returns(false);

alwaysReturnsFalse(); // false

Calling arbitrary functions (AKA stubs with side effects)

Section titled Calling arbitrary functions (AKA stubs with side effects)

Use callsFake for this (reference):

I have a “matchmaker” that has a function called “fetchMoreMatchmakePlayers” that reaches out to another server. While testing, I don’t want to actually reach out to another server, but I do need to act like more players were added to the matchmaker.

sinon.stub(matchmaker, 'fetchMoreMatchmakePlayers').callsFake(() => {
matchmaker.addPlayer(4, 105);
matchmaker.addPlayer(5, 102);
matchmaker.addPlayer(6, 106);
return Promise.resolve();
});

I highly suggest reading the reference link to find out when the developers themselves suggest using Mocks as they say that it should ideally only be once per unit test, and even then, you could probably just use asserts if you want instead of a mock.

Assertions are pretty straightforward in that they’re really just mirrors of the spy/stub APIs for the most part, but they’re useful because you’ll get extra information if they fail.

function sum(x, y) {

return x + y;

}

const spy = sinon.spy(sum);

spy(5, 6);

// We only called it once, so this produces an Error.

sinon.assert.calledTwice(spy);

D:CodeJavaScriptlearningsinonnode _modulessinonlibsinonassert.js:93

throw error;

^

AssertError: expected sum to be called twice but was called once

sum(5, 6) => 11 at Object.<anonymous> (D:CodeJavaScriptlearningsinonmain .js:14:1)

If you’re expecting an assertion, make sure you still have a try/catch

Section titled If you’re expecting an assertion, make sure you still have a try/catch

Sinon doesn’t magically catch your errors for you.

const person = new Person();
const spy = sinon.spy(person, 'validateName');
person.name = 12345;
try {
person.validateName();
} catch (error) {
sinon.assert.threw(spy);
}

Also, make sure the “sinon.assert.threw” isn’t in a try/catch or else you won’t see when it doesn’t catch the error.

Instead of relying on time passing for testing timeouts of functions (or even just intervals), you can use fake timers instead. Note that the Jest equivalents are timer mocks and Date mocks.

function fakeTimeFunction(callback) {
setTimeout(function () {
callback();
}, 100);
}
const clock = sinon.useFakeTimers();
const callback = () => {
console.log('Complete!');
}
const callbackSpy = sinon.spy(callback);
fakeTimeFunction(callbackSpy);
clock.tick(99);
sinon.assert.notCalled(callbackSpy);
clock.tick(1);
sinon.assert.called(callbackSpy);
console.log('Restoring clock');
clock.restore();

If your fake timers don’t seem to be working, then it could be that you haven’t installed them soon enough. I ran into an issue in Mocha where I was creating an object in “beforeEach” (as in “before running each test, do this”) but calling useFakeTimers from sinonTest.

UPDATE: as of Sinon v2.0, sinon.test, sinon.testCase, and sinon.config were moved into their own module (reference).

If you’re ever changing global state using Sinon and you want to eventually restore the old one, a sandbox could be helpful. You can even have Sinon manage them automatically by calling sinon.test(yourTestFunction).

I don’t totally see how these are helpful since most of the time you’re making spies/stubs/mocks that don’t modify the originals.

// Imagine that this is a persistent object or global state that you’re modifying with Sinon.

const persistentObject = {

sum: (x,y) => x + y,

functionThatUsesSum: (x, y) => this.sum(x, y)

};

sinon.test(() => {

// All sinon-related changes inside of this function will undone at the end

persistentObject.sum = sinon.spy(persistentObject.sum);

persistentObject.functionThatUsesSum(5, 6);

sinon.assert.calledOnce(persistentObject.sum);

});

// Sinon restored the original ‘sum’ function once we were outside of our

// sinon.test scope, so this will fail since the function is no longer a spy.

sinon.assert.calledOnce(persistentObject.sum);

There are some other features that aren’t necessarily at the forefront of Sinon. For example, there are fake XHR requests, but you could always use a library like “nock” to easily mock HTTP traffic.