Mocking JavaScript a little - test doubles with sinon.js, mocha and chai
How do you write a test in which you cannot (or chose not to) use real dependencies of whatever is that you’re testing? What are your options in JavaScript? And what does a test doubles library give you that plain-old-js can’t? Interested to find out? Read on!
System under test
Let’s assume we have a situation similar to the one described by Uncle Bob Martin in “The Little Mocker”1.
We have a System
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
that delegates the process of logging users in to the Authoriser
, which implementation might look more or less like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
A dummy
How can we prove that a newly created System
has no logged in users?
And do we even need to construct a real Authoriser
object for that?
Since we know that calling system.loginCount
does not even touch the authoriser
object,
we can replace the authoriser
with a dummy:
1 2 3 4 5 6 7 8 9 10 |
|
And that’s all there is to a dummy! You pass it into something when you have to provide an argument, but you know that it will never be used.
A stub
Let’s now suppose that you want to test a part of your System
that requires you to be logged in.
Of course you might say that you could just log in, but you’ve already tested that login works, and since the process of logging in takes time, why do it twice? Also, if there’s a bug in login your test would fail too! That’s an unnecessary coupling that can be easily avoided with a stub:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
So what does this stubbed accepting_authoriser
do? It returns true, therefore accepting any username/password pairs.
If you now wanted to test another part of your system that handles unauthorised users you could create a rejecting_authoriser
like this:
1
|
|
So that’s a stub, it simply returns a value and has no other logic in it.
Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.
Beautiful! So nice and simple, isn’t it? Just one line of code! Isn’t duck typing brilliant?
Imagine how much more code you’d have to write if you were using Java or C#!
You’d probably have to create an Authoriser
interface in the first place,
then make sure both the real implementation and your stub implement
that interface
, then …
Oh yeah, the interface
… We don’t have this construct in JavaScript, do we?
The trouble with using plain-old-js for hand-rolling your test stubs is that you won’t get a meaningful error when the interface of the stubbed-out object changes and your test stubs are no longer correct. In worst case scenario you might not even get an error at all!
What could be the consequences of this, you may ask? Well, I’ve seen huge test suites pass even though the application they were supposed to test was fundamentally broken as half the code didn’t even exist anymore… Trust me, you don’t want to be there2 :–)
Right, so this situation is not cool, but can we do better than that? Yes we can! Enter sinon.js3!
How would the accepting_authoriser
look implemented with sinon then?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
And what makes it different? Calling sinon.createStubInstance(Authoriser)
creates a stub object
that allows you to stub out only those methods that exist on the prototype
of the original Authoriser
.
Now that’s important because it means that should the #authorise
method happily decide to change its
name to say #isAuthorised
one day, you’d get a TypeError
in your test,
exactly where you attempt to stub out the no longer existing #authorise
. Cool, right?
A spy
Do you remember when I said in the beginning that System
delegates the process of logging users in to the Authoriser
?
How can we test whether or not this interaction really takes place?
What we’d need to do is spy on the caller to see inside the workings of the algorithm we’re testing –
and that’s precisely what spies are for4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
You inject a spy object in exactly the same way you’d inject a stub, but then at the end of your test you check if the interactions recorded by your spy are the ones you expected it to see.
Spies are stubs that also record some information based on how they were called.
Another thing to note is that
because a spy maintains state (authorise_was_called
flag in this example), it needs to be reset between the tests
to avoid one test case affecting another – this is done in the afterEach
block.
Of course our hand-rolled implementation suffers from the problems I described earlier when we talked about hand-rolling stubs. Let’s improve our previous implementation with sinon:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Similarly to the stub example, I’m also using sinon.createStubInstance
here.
There’s one significant difference between our hand-rolled spy implementation and the one above though:
sinon spy itself is not the main object you inject, it’s a wrapper around object’s method.
That’s why I inject the accepting_authoriser
but perform the assertion on accepting_authoriser.authorise
spy and
use accepting_authoriser.authorise.restore()
to reset it.
Additionally, the fact that sinon spy is a wrapper means that if you wanted to spy
on the real Authoriser's
method you could do it like so:
1 2 3 4 5 6 7 |
|
A mock
A mock is not so interested in the return values of functions.
It’s more interested in what function were called, with what arguments, when, and how often.
Another thing that makes a mock5 different from a stub is its verify
method,
which groups all the assertions and performs them at the end of a test, verifying our System's
behaviour:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Now that we understand how mocks work and we know how to build them ourselves, let’s see how sinon can make things easier. There’s an essential distinction we need to make before diving into sinon mocks though:
In Sinon, the basic unit of faking is functions, always functions.
So a “stub object” in Java-type literature translates to a “stub function/method” in Sinon/JavaScript land.
… and same goes for sinon mocks. OK, but what does this mean to you? Let’s have a look at differences in the implementation first:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
An important thing to note here is that sinon mock is created as a wrapper around
an instance of the real Authoriser
, but instead of the mock we’re injecting the already mentioned real instance
into the System
.
This strategy might be problematic if instantiation of your depended-on object (Authoriser
in our example)
is expensive; If that’s the case you might want to avoid calling the constructor directly by using Object.create
6:
1 2 |
|
Note: At the time of writing sinon.js (version 1.10.3) does not provide any equivalent of createStubInstance
for mocks.
A fake
Fake has business behavior. You can drive a fake to behave in different ways by giving it different data.
Suppose we wanted to define several personas to whom our System
responded differently.
Let’s say “bob” knows his password and “alice” forgot it; My personal recommendation
would be to use two separate stubs, but let’s talk about fakes as some might prefer to use them instead:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
… and you could also achieve the same result with sinon:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Be very careful with fakes as they can quickly become extremely complicated. If you can use stubs instead – please do. If you can’t then perhaps it just highlighted that the thing you’re trying to test is too complex and needs breaking down?
Tools
Techniques described in this article can be applied to both front-end and back-end (node.js) development, same applies to tools I used in the above examples, namely:
- mocha – a popular JavaScript test framework
- sinon.js – test doubles library
- chai.js and its bdd-style assertion library
- sinon-chai – sinon.js assertions for chai
Final thoughts and a word of warning
Bear in mind that the more you spy and mock, the tighter you couple your tests to the implementation of your system. This leads to fragile tests that may break for reasons that shouldn’t break them.
Test doubles should be used with care and applied only when they’re the best tool for the task at hand. Just like with any other tool in your development tool belt: don’t use a screwdriver to knock in a nail just because you got a new screwdriver and want to try it out ;–)
-
The Little Mocker blog post by Uncle Bob Martin. As you might have already noticed, Uncle Bob’s post has inspired this article greatly!↩
-
If you already are in this situation get in touch, I can help you to get out of it :–) OK, no more cheeky marketing. Get back to the article, quick, quick:↩
-
Sinon.js was written by Christian Johansen, author of this excellent book↩
-
To better understand what spies and other test doubles are and how to use them I highly recommend reading this book↩
-
The concept of “mock objects” was originally introduced by Tim Mackinnon, Steve Freeman and Philip Craig in their paper Endo-Testing: Unit Testing with Mock Objects. Steve Freeman is a co-author of the GOOSE book. Again, highly recommended.↩