Sinon Tutorial: JavaScript Testing with Mocks, Spies & Stubs
This article was peer reviewed by Mark Brown and MarcTowler. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
One of the biggest stumbling blocks when writing unit tests is what to do when you have code that’s non-trivial.
In real life projects, code often does all kinds of things that make testing hard. Ajax requests, timers, dates, accessing other browser features… or if you’re using Node.js, databases are always fun, and so is network or file access.
All of these are hard to test because you can’t control them in code. If you’re using Ajax, you need a server to respond to the request, so as to make your tests pass. If you use setTimeout
, your test will have to wait. With databases or networking, it’s the same thing — you need a database with the correct data, or a network server.
Real-life isn’t as easy as many testing tutorials make it look. But did you know there is a solution?
By using Sinon, we can make testing non-trivial code trivial!
Let’s find out how.
What Makes Sinon so Important and Useful?
Put simply, Sinon allows you to replace the difficult parts of your tests with something that makes testing simple.
When testing a piece of code, you don’t want to have it affected by anything outside the test. If something external affects a test, the test becomes much more complex and could fail randomly.
If you want to test code making an Ajax call, how can you do that? You need to run a server and make sure it gives the exact response needed for your test. It’s complicated to set up, and makes writing and running unit tests difficult.
And what if your code depends on time? Let’s say it waits one second before doing something. What now? You could use a setTimeout
in your test to wait one second, but that makes the test slow. Imagine if the interval was longer, for example five minutes. I’m going to guess you probably don’t want to wait five minutes each time you run your tests.
By using Sinon, we can take both of these issues (plus many others), and eliminate the complexity.
How Does Sinon Work?
Sinon helps eliminate complexity in tests by allowing you to easily create so called test-doubles.
Test-doubles are, like the name suggests, replacements for pieces of code used in your tests. Looking back at the Ajax example, instead of setting up a server, we would replace the Ajax call with a test-double. With the time example, we would use test-doubles to allow us to “travel forwards in time”.
It may sound a bit weird, but the basic concept is simple. Because JavaScript is very dynamic, we can take any function and replace it with something else. Test-doubles just take this idea a little bit further. With Sinon, we can replace any JavaScript function with a test-double, which can then be configured to do a variety of things to make testing complex things simple.
Sinon splits test-doubles into three types:
- Spies, which offer information about function calls, without affecting their behavior
- Stubs, which are like spies, but completely replace the function. This makes it possible to make a stubbed function do whatever you like — throw an exception, return a specific value, etc
- Mocks, which make replacing whole objects easier by combining both spies and stubs
In addition, Sinon also provides some other helpers, although these are outside the scope of this article:
- Fake timers, which can be used to travel forwards in time, for example triggering a
setTimeout
- Fake XMLHttpRequest and server, which can be used to fake Ajax requests and responses
With these features, Sinon allows you to solve all of the difficult problems external dependencies cause in your tests. If you learn the tricks for using Sinon effectively, you won’t need any other tools.
Installing Sinon
First off we need to install Sinon.
For Node.js testing:
- Install Sinon via npm using
npm install sinon
- Require Sinon in your test with
var sinon = require('sinon');
For browser based testing:
- You can either install Sinon via npm with
npm install sinon
, use a CDN, or download it from Sinon’s website - Include
sinon.js
in your test runner page.
Getting Started
Sinon has a lot of functionality, but much of it builds on top of itself. You learn about one part, and you already know about the next one. This makes Sinon easy to use once you learn the basics and know what each different part does.
We usually need Sinon when our code calls a function which is giving us trouble.
With Ajax, it could be $.get
or XMLHttpRequest
. With time, the function might be setTimeout
. With databases, it could be mongodb.findOne
.
To make it easier to talk about this function, I’m going to call it the dependency. The function we are testing depends on the result of another function.
We can say, the basic use pattern with Sinon is to replace the problematic dependency with a test-double.
- When testing Ajax, we replace
XMLHttpRequest
with a test-double which pretends to make an Ajax request - When testing time, we replace
setTimeout
with a pretend timer - When testing database access, we could replace
mongodb.findOne
with a test-double which immediately returns some fake data
Let’s see how that works in practice.
Spies
Spies are the simplest part of Sinon, and other functionality builds on top of them.
The primary use for spies is to gather information about function calls. You can also use them to help verify things, such as whether a function was called or not.
var spy = sinon.spy();
//We can call a spy like a function
spy('Hello', 'World');
//Now we can get information about the call
console.log(spy.firstCall.args); //output: ['Hello', 'World']
The function sinon.spy
returns a Spy
object, which can be called like a function, but also contains properties with information on any calls made to it. In the example above, the firstCall
property has information about the first call, such as firstCall.args
which is the list of arguments passed.
Although you can create anonymous spies as above by calling sinon.spy
with no parameters, a more common pattern is to replace another function with a spy.
var user = {
...
setName: function(name){
this.name = name;
}
}
//Create a spy for the setName function
var setNameSpy = sinon.spy(user, 'setName');
//Now, any time we call the function, the spy logs information about it
user.setName('Darth Vader');
//Which we can see by looking at the spy object
console.log(setNameSpy.callCount); //output: 1
//Important final step - remove the spy
setNameSpy.restore();
Replacing another function with a spy works similarly to the previous example, with one important difference: When you’ve finished using the spy, it’s important to remember to restore the original function, as in the last line of the example above. Without this your tests may misbehave.
Spies have a lot of different properties, which provide different information on how they were used. Sinon’s spy documentation has a comprehensive list of all available options.
In practice, you might not use spies very often. You’re more likely to need a stub, but spies can be convenient for example to verify a callback was called:
function myFunction(condition, callback){
if(condition){
callback();
}
}
describe('myFunction', function() {
it('should call the callback function', function() {
var callback = sinon.spy();
myFunction(true, callback);
assert(callback.calledOnce);
});
});
In this example I am using Mocha as the test framework and Chai as the assertion library. If you would like to learn more about either of these, then please consult my previous article: Unit Test Your JavaScript Using Mocha and Chai.
See the Pen Sinon Tutorial: JavaScript Testing with Mocks, Spies & Stubs by SitePoint (@SitePoint) on CodePen.
Sinon’s Assertions
Before we carry on and talk about stubs, let’s take a quick detour and look at Sinon’s assertions.
In most testing situations with spies (and stubs), you need some way of verifying the result of the test.
We can use any kind of assertion to verify the results. In the previous example with the callback, we used Chai’s assert
function which ensures the value is truthy.
assert(callback.calledOnce);
The problem with this is that the error message in a failure is unclear. You’ll simply be told “false was not true”, or some variation of that. As you can probably imagine, it’s not very helpful in finding out what went wrong, and you need to go look at the source code for the test to figure it out. Not fun.
To fix the problem, we could include a custom error message into the assertion.
assert(callback.calledOnce, 'Callback was not called once');
But why bother when we can use Sinon’s own assertions?
describe('myFunction', function() {
it('should call the callback function', function() {
var callback = sinon.spy();
myFunction(true, callback);
sinon.assert.calledOnce(callback);
});
});
Using Sinon’s assertions like this gives us a much better error message out of the box. This becomes very useful when you need to verify more complex condition, such as the parameters to a function.
Here are some examples of other useful assertions provided by Sinon:
sinon.assert.calledWith
can be used to verify the a function was called with specific parameters (this is probably the one I use most often)sinon.assert.callOrder
can verify functions were called in a specific order
As with spies, Sinon’s assertion documentation has all the options available. If you like using Chai, there is also a sinon-chai plugin available, which lets you use Sinon assertions through Chai’s expect
or should
interface.
Stubs
Stubs are the go-to test-double because of their flexibility and convenience. They have all the functionality of spies, but instead of just spying on what a function does, a stub completely replaces it. In other words, when using a spy, the original function still runs, but when using a stub, it doesn’t.
This makes stubs perfect for a number of tasks, such as:
- Replacing Ajax or other external calls which make tests slow and difficult to write
- Triggering different code paths depending on function output
- Testing unusual conditions, for example what happens when an exception is thrown?
We can create stubs in a similar way to spies…
var stub = sinon.stub();
stub('hello');
console.log(stub.firstCall.args); //output: ['hello']
We can create anonymous stubs as with spies, but stubs become really useful when you use them to replace existing functions.
For example, if we have some code that uses jQuery’s Ajax functionality, testing it is difficult. The code sends a request to whatever server we’ve configured, so we need to have it available, or add a special case to the code to not do that in a test environment — which is a big no-no. You should almost never have test-specific cases in your code.
Instead of resorting to poor practices, we can use Sinon and replace the Ajax functionality with a stub. This makes testing it trivial.
Here’s an example function we’ll test. It takes an object as its parameter, and sends it via Ajax to a predefined URL.
function saveUser(user, callback) {
$.post('/users', {
first: user.firstname,
last: user.lastname
}, callback);
}
Normally, testing this would be difficult because of the Ajax call and predefined URL, but if we use a stub, it becomes easy.
Let’s say we want to ensure the callback function passed to saveUser
is called correctly once the request finishes.
describe('saveUser', function() {
it('should call callback after saving', function() {
//We'll stub $.post so a request is not sent
var post = sinon.stub($, 'post');
post.yields();
//We can use a spy as the callback so it's easy to verify
var callback = sinon.spy();
saveUser({ firstname: 'Han', lastname: 'Solo' }, callback);
post.restore();
sinon.assert.calledOnce(callback);
});
});
See the Pen Sinon Tutorial: JavaScript Testing with Mocks, Spies & Stubs by SitePoint (@SitePoint) on CodePen.
Here, we replace the Ajax function with a stub. This means the request is never sent, and we don’t need a server or anything — we have full control over what happens in our test code!
As we want to ensure the callback we pass into saveUser
gets called, we’ll instruct the stub to yield. This means the stub automatically calls the first function passed as a parameter to it. This mimics the behavior of $.post
, which would call a callback once the request has finished.
In addition to a stub, we’re creating a spy in this test. We could use a normal function as the callback, but using a spy makes it easy to verify the result of the test using Sinon’s sinon.assert.calledOnce
assertion.
In most cases when you need a stub, you can follow the same basic pattern:
- Find the problematic function, such as
$.post
- Look at how it works so you can mimic it in the test
- Create a stub
- Set the stub to have the behavior you want in your test
The stub doesn’t need to mimic every behavior. Only the behavior you need for the test is necessary, and anything else can be left out.
Another common usage for stubs is verifying a function was called with a specific set of arguments.
For example, for our Ajax functionality, we want to ensure the correct values are being sent. Therefore, we could have something like:
describe('saveUser', function() {
it('should send correct parameters to the expected URL', function() {
//We'll stub $.post same as before
var post = sinon.stub($, 'post');
//We'll set up some variables to contain the expected results
var expectedUrl = '/users';
var expectedParams = {
first: 'Expected first name',
last: 'Expected last name'
};
//We can also set up the user we'll save based on the expected data
var user = {
firstname: expectedParams.first,
lastname: expectedParams.last
}
saveUser(user, function(){} );
post.restore();
sinon.assert.calledWith(post, expectedUrl, expectedParams);
});
});
See the Pen Sinon Tutorial: JavaScript Testing with Mocks, Spies & Stubs by SitePoint (@SitePoint) on CodePen.
Again, we create a stub for $.post()
, but this time we don’t set it to yield. This test doesn’t care about the callback, therefore having it yield is unnecessary.
We set up some variables to contain the expected data — the URL and the parameters. It’s a good practice to set up variables like this, as it makes it easy to see at a glance what the requirements for the test are. It also helps us set up the user
variable without repeating the values.
This time we used the sinon.assert.calledWith()
assertion. We pass the stub as its first parameter, because this time we want to verify the stub was called with the correct parameters.
There’s also another way of testing Ajax requests in Sinon. This is by using Sinon’s fake XMLHttpRequest functionality. We won’t go into detail on it here, but if you want to learn how that works, see my article on Ajax testing with Sinon’s fake XMLHttpRequest.
Mocks
Mocks are a different approach to stubs. If you’ve heard the term “mock object”, this is the same thing — Sinon’s mocks can be used to replace whole objects and alter their behavior similar to stubbing functions.
They are primarily useful if you need to stub more than one function from a single object. If you only need to replace a single function, a stub is easier to use.
You should take care when using mocks! Because of their power, it’s easy to make your tests overly specific — test too many and too specific things — which risks making your tests unintentionally brittle.
Unlike spies and stubs, mocks have assertions built-in. You define your expected results up front by telling the mock object what needs to happen, and then calling the verification function at the end of the test.
Let’s say we’re using store.js to save things into localStorage, and we want to test a function related to that. We can use a mock to help testing it like so:
describe('incrementStoredData', function() {
it('should increment stored value by one', function() {
var storeMock = sinon.mock(store);
storeMock.expects('get').withArgs('data').returns(0);
storeMock.expects('set').once().withArgs('data', 1);
incrementStoredData();
storeMock.restore();
storeMock.verify();
});
});
See the Pen Sinon Tutorial: JavaScript Testing with Mocks, Spies & Stubs by SitePoint (@SitePoint) on CodePen.
When using mocks, we define the expected calls and their results using a fluent calling style as seen above. This is the same as using assertions to verify test results, except we define them up-front, and to verify them, we call storeMock.verify()
at the end of the test.
In Sinon’s mock object terminology, calling mock.expects('something')
creates an expectation. As in, the method mock.something()
expects to be called. Each expectation, in addition to mock-specific functionality, supports the same functions as spies and stubs.
You may find that it’s often much easier to use a stub than a mock — and that’s perfectly fine. Mocks should be used with care.
For a full list of mock-specific functions, check Sinon’s mock documentation.
Important Best Practice: Use sinon.test()
There is one important best practice with Sinon that should be remembered whenever using spies, stubs or mocks.
If you replace an existing function with a test-double, use sinon.test()
.
In the earlier example, we used stub.restore()
or mock.restore()
to clean up after using them. This is necessary as otherwise the test-double remains in place, and could negatively affect other tests or cause errors.
But using the restore()
function directly is problematic. It’s possible that the function being tested causes an error and ends the test function before restore()
has been called!
We have two ways to solve this: We can wrap the whole thing in a try catch
block. This allows us to put the restore()
call in a finally
block, ensuring it gets run no matter what.
Or, a better approach, we can wrap the test function with sinon.test()
it('should do something with stubs', sinon.test(function() {
var stub = this.stub($, 'post');
doSomething();
sinon.assert.calledOnce(stub);
});
In the above example, note the second parameter to it()
is wrapped within sinon.test()
. The second thing of note is that we use this.stub()
instead of sinon.stub()
.
Wrapping a test with sinon.test()
allows us to use Sinon’s sandboxing feature, allowing us to create spies, stubs and mocks via this.spy()
, this.stub()
and this.mock()
. Any test-doubles you create using sandboxing are cleaned up automatically.
Note that our example code above has no stub.restore()
— it’s unnecessary thanks to the test being sandboxed.
If you use sinon.test()
where possible, you can avoid problems where tests start failing randomly because an earlier test didn’t clean up its test-doubles due to an error.
Sinon Is Not Magic
Sinon does many things, and occasionally it might seem difficult to understand how it works. Let’s take a look at some plain JavaScript examples of how Sinon works, so we can get a better idea of what it does under the hood. This will help you use it more effectively in different situations.
We can create spies, stubs and mocks manually too. The reason we use Sinon is it makes the task trivial — creating them manually can be quite complicated, but let’s see how that works, to understand what Sinon does.
First, a spy is essentially a function wrapper:
//A simple spy helper
function createSpy(targetFunc) {
var spy = function() {
spy.args = arguments;
spy.returnValue = targetFunc.apply(this, arguments);
return spy.returnValue;
};
return spy;
}
//Let's spy on a simple function:
function sum(a, b) { return a + b; }
var spiedSum = createSpy(sum);
spiedSum(10, 5);
console.log(spiedSum.args); //Output: [10, 5]
console.log(spiedSum.returnValue); //Output: 15
We can get spy functionality quite easily with a custom function like so. But notice that Sinon’s spies provide a much wider array of functionality — including assertion support. This makes Sinon a lot more convenient.
What about a Stub Then?
To make a really simple stub, you can simply replace a function with a new one:
var stub = function() { };
var original = thing.otherFunction;
thing.otherFunction = stub;
//Now any calls to thing.otherFunction will call our stub instead
But again, there are several advantages Sinon’s stubs provide:
- They have the full spy functionality in them
- You can restore original behavior easily with
stub.restore()
- You can assert against Sinon stubs
Mocks simply combine the behavior of spies and stubs, making it possible to use their features in different ways.
Even though Sinon may sometimes seem like it does a lot of “magic”, this can be done fairly easily with your own code too, for the most part. Sinon is just much more convenient to use, than having to write your own library for the purpose.
Conclusion
Testing real-life code can sometimes seem way too complex and it’s easy to give up altogether. But with help from Sinon, testing virtually any kind of code becomes a breeze.
Just remember the main principle: If a function makes your test difficult to write, try replacing it with a test-double. This principle applies regardless of what the function does.
Looking to learn more about how to apply Sinon with your own code? Head over to my site and I’ll send you my free Sinon in the real-world guide, which includes Sinon best practices, and three real-world examples of how to apply it in different types of testing situations!