JavaScript is very dynamic language - nothing is cast in stone,
not even the console
object. You can add, delete and replace
console methods at will. It might be a bad idea, but it is very
simple. Let's extend console
by adding console.foo()
method
that just prints string foo on the screen.
1 | console.foo = function () { |
In order to properly unit test the added method, we need to
make sure it calls console.log
with right arguments. The only
way to test this is by spying on the log
method.
Spying ourselves
We can replace the default console.log
method with our
own implementation inside the unit test. In this example I will
use mocha, but the choice
of test runner is irrelevant.
1 | it('calls console.log', function () { |
console.log
is a simple function, it does not access its owner console
object, so we do not need to worry about binding the context this
when
storing reference prev = console.log
. Otherwise we would need to do
prev = console.log.bind(console)
call.
It is better to install the fake spy method and restore it in separate setup / teardown functions, rather than inside a unit test, like this:
1 | describe('console.foo', function () { |
First rule to remember:
Cleanup the spies at the same logical level as their setup.
For example, if you setup a spy inside a unit test, clean it up inside the
unit test. If you setup a spy at a suite level (inside beforeEach
), then
do not clean it up inside the unit test. Keeps you test code much easier to
understand.
Second rule:
Do not disable the original functionality. Call the method spied on.
In the example above, we should really call the original console.log
to pass along the arguments, even to see any messages from the assertions inside
the unit test. We can easily do this using JavaScript apply
function call
1 | beforeEach(function () { |
Spying by wrapping
Instead of creating temp function and calling the original method ourselves, let's use meld
1 | var meld = require('meld'); |
Professional spying with sinon.js
In the previous examples we only kept track of two pieces of information:
the function wrapper and if it was called. You can imagine how number of
temp variables would grow if we wanted to check if console.log
was called
the right number of times and with correct arguments (only foo
must be passed).
Implementing all these features is unnecessary; let's use
sinon.js that implements all these features and more.
To use sinon.js in the browser, download the latest built version of the library
from sinon website by clicking on "Download Sinon.js
You can get a sense how powerful this library is by opening node REPL after installing sinon and inspecting a spied upon method. In this case, the original function has been augmented with lots of utility methods, mostly used for keeping track how many times it was called, each call's arguments and the returned values.
$ node
> var sinon = require('sinon')
> console.log
[Function]
> sinon.spy(console.log)
{ [Function: proxy]
reset: [Function],
invoke: [Function: invoke],
getCall: [Function: getCall],
getCalls: [Function],
calledBefore: [Function: calledBefore],
calledAfter: [Function: calledAfter],
withArgs: [Function],
matches: [Function],
printf: [Function],
calledOn: [Function],
alwaysCalledOn: [Function],
calledWith: [Function],
calledWithMatch: [Function],
alwaysCalledWith: [Function],
alwaysCalledWithMatch: [Function],
calledWithExactly: [Function],
alwaysCalledWithExactly: [Function],
neverCalledWith: [Function],
neverCalledWithMatch: [Function],
threw: [Function],
alwaysThrew: [Function],
returned: [Function],
alwaysReturned: [Function],
calledWithNew: [Function],
alwaysCalledWithNew: [Function],
callArg: [Function],
callArgWith: [Function],
callArgOn: [Function],
callArgOnWith: [Function],
yield: [Function],
invokeCallback: [Function],
yieldOn: [Function],
yieldTo: [Function],
yieldToOn: [Function],
formatters:
{ c: [Function],
n: [Function],
C: [Function],
t: [Function],
'*': [Function] },
spyCall: undefined,
called: false,
notCalled: true,
calledOnce: false,
calledTwice: false,
calledThrice: false,
callCount: 0,
firstCall: null,
secondCall: null,
thirdCall: null,
lastCall: null,
args: [],
returnValues: [],
thisValues: [],
exceptions: [],
callIds: [],
displayName: 'spy',
toString: [Function: toString],
_create: [Function: create],
id: 'spy#0' }
Let's use this in our test suite
1 | describe('console.foo with sinon', function () { |
sinon.js is quick, simple and powerful, and we have only used one feature. There are also stubs, mock XMLHttpRequests and fake timers. It is a must library for JavaScript testing.