R
E
A
C
T
O

xamples
epeat
ode
pproach
ptimize
est
Make a Spy

Spies
Testing and assertion libraries include Jasmine, Mocha, Chai, Sinon, etc.
Many have a special feature called spies. Spies allow test specs to track how specific functions of interest are used:
- whether they are called,
- how many times they are called,
- what they are called with,
- what they return,
- if they throw errors and more

Question
Implement a 'spyOn' function which does the following:
- Takes a function, 'func', as its parameter
- Returns a spy function, 'spy', that takes any number of arguments
- 'spy' calls 'func' with the given arguments and returns what 'func' returns
-
'spy' has the following methods:
- .getCallCount() : returns the number of times 'spy' has been called
- .wasCalledWith(val) : returns boolean of 'spy' ever being called with 'val'
- .returned(val) : returns boolean of 'spy' ever returning 'val'

Sample Output
function adder(n1, n2) { return n1 + n2; }
var adderSpy = spyOn( adder );
adderSpy.getCallCount(); // 0
adderSpy(2, 4); // returns 6
adderSpy.getCallCount(); // 1
adderSpy(3, 5); // returns 8
adderSpy.getCallCount(); // 2
adderSpy.wasCalledWith(2); // true
adderSpy.wasCalledWith(0); // false
adderSpy.returned(6); // true
adderSpy.returned(9); // false
Keep in mind:
- not all functions take two arguments
- .wasCalledWith and .returned do not need to be the most recent values

1. Create the SpyOn function
var spyOn = function(func) {
// keep track of function call count, start at 0
var callCount = 0;
// array to store call results
var returnedVals = [];
// array to store call arguments
var calledWithVals = [];
//function to be returned
var spy = function () {
/** code will go here **/
};
//create required methods on spy
spy.getCallCount = function() {
return callCount;
};
spy.wasCalledWith = function (val) {
// search through returnedVals array for val
};
spy.returned = function (val) {
// search through calledWithVals array for val
};
return spy;
}

2. Figure out how to access function arguments
// Remember that function arguments are an array-like objects?
// Turn arguments into an actual array
// Array.prototype.slice.call(arguments)
//Now we can use the resulting array to call .apply() on our function
var spy = function() {
var args = [].slice.call(arguments);
var retVal = func.apply(func, args);
/* More code will go here */
};

3. Save the returned and called values in the proper arrays and increment call count
function spyOn (func) {
let callCount = 0,
calledWithVals = [],
returnedVals = [];
function spy () {
let args = [].slice.call(arguments),
returnVal = func.apply(this, args);
//store result of applying function
returnedVals.push(returnVal);
// add arguments array values to called values array
calledWithVals = calledWithVals.concat(args);
//increment function call count by 1
callCount++;
//return the result of the function call
return returnVal;
};
spy.getCallCount;
spy.wasCalledWith;
spy.returned;
return spy;
};

4. Implement the 'wasCalledWith' and 'returned' methods
// the 'indexOf' method of an Array returns '-1' if a value is not found
// we can use this to return a boolean value for the two search methods
spy.getCallCount = function () {
return callCount;
};
spy.wasCalledWith = function (val) {
return (callVals.indexOf(val) > -1);
};
spy.returned = function (val) {
return (retVals.indexOf(val) > -1);
};
return spy;

Final Solution
function spyOn (func) {
let callCount = 0,
calledWithVals = [],
returnedVals = [];
function spy () {
let args = [].slice.call(arguments),
returnVal = func.apply(this, args);
returnedVals.push(returnVal);
calledWithVals = calledWithVals.concat(args);
callCount++;
return returnVal;
}
spy.getCallCount = function () {
return callCount;
};
spy.wasCalledWith = function (val) {
return (calledWithVals.indexOf(val) > -1);
};
spy.returned = function (val) {
return (returnedVals.indexOf(val) > -1);
};
return spy;
}

REPPPPLLLLLL
REACTO: Spy
By Kate Humphrey
REACTO: Spy
- 808