Unit Testing with Sinon Spies
Published: 2021-02-22 08:02:52

#Unit Testing

Unit testing is a key tool for auditing, troubleshooting, and maintaining code. The basic principle of a unit test is that it runs on code that is locally available, and it doesn’t create side-effects.

APIs, databases, and other components outside of a coder’s control can complicate the troubleshooting process. That’s where unit testing comes in handy. When unit testing, programmers can emulate the behavior of these components with stand-ins . Not only can these stand-ins emulate the behavior of other parts of a program, they can also provide valuable feedback on how a program is running.

#Sinon Spies

Sinon’s library of modules and methods provide programmers with tools to write powerful unit tests. One of these tools is spies. The functionality of Sinon spies lines up well with the definition of spy. IRL spies covertly collect and report information on a given target’s activities, movements, and plans. This isn’t far off from the behavior of a Sinon spy.

Spies wrap around existing functions, and mimic their functionality. They can also be used as an anonymous function, and, say, be fed into another function as its argument. The benefit of these practices is revealed when looking through the spy’s methods:

```
alwaysCalledOn: ƒ ()
alwaysCalledWith: ƒ ()
alwaysCalledWithExactly: ƒ ()
alwaysCalledWithMatch: ƒ ()
alwaysCalledWithNew: ƒ ()
alwaysReturned: ƒ ()
alwaysThrew: ƒ ()
callArg: ƒ ()
callArgOn: ƒ ()
callArgOnWith: ƒ ()
callArgWith: ƒ ()
calledAfter: ƒ (l)
calledBefore: ƒ (l)
calledImmediatelyAfter: ƒ (l)
calledImmediatelyBefore: ƒ (l)
calledOn: ƒ ()
calledWith: ƒ ()
calledWithExactly: ƒ ()
calledWithMatch: ƒ ()
calledWithNew: ƒ ()
create: ƒ (l,n)
formatters: {c: ƒ, n: ƒ, D: ƒ, C: ƒ, t: ƒ, …}
getCall: ƒ (l)
getCalls: ƒ ()
invoke: ƒ (l,n,u)
invokeCallback: ƒ ()
matches: ƒ (l,n)
matchingFakes: ƒ (l,n)
named: ƒ (l)
neverCalledWith: ƒ ()
neverCalledWithMatch: ƒ ()
printf: ƒ (l)
reset: ƒ ()
returned: ƒ ()
spyCall: ƒ ()
threw: ƒ ()
throwArg: ƒ ()
withArgs: ƒ ()
yield: ƒ ()
yieldOn: ƒ ()
yieldTo: ƒ ()
yieldToOn: ƒ ()
```

As you can see, the spy is set up to reveal information relevant to a function’s behavior. For example, coders can use these methods to:

* Count the number of times a function is called
* Record the order in which functions execute
* Record arguments that are passed into a function
* Store a function's output on a specified call

#Code Example

The function uniqueCall should accept array and callback parameters. Also, if the array includes duplicate values, the callback function should only run on those values which are unique.

```
const uniqueCall = (array, callback) => {

}

let array1 = [1, 2, 2, 3]
let spy1 = sinon.spy()
```

An array with a duplicate value, and a Sinon spy have been initialized. The spy can be used in place of any function. In this instance, it will be given to the uniqueCall function as an argument.

```
const uniqueCall = (array, callback) => {
    array.forEach(value => {
            callback(value)
    })
}

let array1 = [1, 2, 2, 3]
let spy1 = sinon.spy()
uniqueCall(array1, spy1)
```

A v1 of uniqueCall has been implemented. The callCount method can be called on spy1 to see if the function’s behavior matches expectations:

```
> spy1.callCount
< 4
```

Since this isn’t the expected result, a v2 of uniqueCall will need to be implemented:

```
const uniqueCall = (array, callback) => {
    const calledValues = [];
    array.forEach(value => {
        if (!calledValues.includes(value)) {
            calledValues.push(value)
            callback(value)
        }
    })
}

let array1 = [1, 2, 2, 3]
let spy1 = sinon.spy()
uniqueCall(array1, spy1)
```

```
> spy1.callCount
< 3
```

This is a fairly simple example of how spies and their methods can be utilized for testing. When used to debug and optimize code, Sinon gives programmers valuable feedback so they can quickly make necessary changes.