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 

function spyOn (func) {

  // keep track of function call count, 
  // initialize arrays to store results & arguments
  let [callCount, calledWith, returnVals] = [0, [], []];
    
  // function to be returned
  function spy (...args) {
    // 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 object? 
// Turn arguments into an actual array  
// Array.prototype.slice.call(arguments)

// Then we can use the resulting array to call .apply() on our function

function spy () {
  let args = [].slice.call(arguments);
  let returnVal = func.apply(this, args);

  // more code to follow
}


// We can also do this without using the 'arguments' object
// by using ES6's 'rest' and 'spread' operators.

function spy (...args) { // rest operator
  let returnVal = func(...args); // spread operator
  
  // more code to follow
}

3. Save the returned and called values in the proper arrays and increment call count

function spyOn (func) {
  
  let [callCount, calledWith, returnVals] = [0, [], []];

  function spy (...args) {
    let returnVal = func(...args);    

    // store result of applying function
    returnVals.push(returnVal);

    // add arguments array values to called values array
    calledWith = calledWith.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.wasCalledWith = function (val) { 
  return calledWith.indexOf(val) > -1; 
};

spy.returned = function (val) { 
  return returnVals.indexOf(val) > -1; 
};


// We can also use ES7's 'includes' method

spy.wasCalledWith = function (val) {
  return calledWith.includes(val);
};

spy.returned = function (val) {
  return returnVals.includes(val);
};

Final Solution

function spyOn (func) {
  
  let [callCount, calledWith, returnVals] = [0, [], []];

  function spy (...args) {

    let returnVal = func(...args);

    returnVals.push(returnVal);
    calledWith = calledWith.concat(args);
    callCount++;

    return returnVal;
  }

  spy.getCallCount = function () { 
    return callCount; 
  };

  spy.wasCalledWith = function (val) { 
    return calledWith.includes(val); 
  };

  spy.returned = function (val) { 
    return returnVals.includes(val); 
  };

  return spy;
}

REPPPPLLLLLLS

repl.it (ES6)

repl.it (ES5)

REACTO: Spy

By Bryan Gergen

REACTO: Spy

  • 1,500