sinon
Created: 2016-05-23 12:26:56 -0700 Modified: 2019-11-19 12:20:00 -0800
Reference
Section titled ReferenceThese 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.
Example
Section titled Examplefunction 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
Another example
Section titled Another exampleInstrumenting 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:
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
Troubleshooting
Section titled TroubleshootingAttempted 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:
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.
Example
Section titled Examplevar 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.
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 (reference)
Section titled Assertions (reference)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.
Example
Section titled Examplefunction 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/catchSinon doesn’t magically catch your errors for you.
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.
Fake timers (reference)
Section titled Fake timers (reference)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.
Example
Section titled ExampleTroubleshooting
Section titled TroubleshootingIf 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.
Sandboxing (reference)
Section titled Sandboxing (reference)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.
Example
Section titled Example// 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);
Other features (e.g. matchers)
Section titled Other features (e.g. matchers)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.