Montague's son Romeo and his friends (Benvolio and Mercutio) hear of the party and resolve to go in disguise. Romeo hopes to see his beloved Rosaline at the party. Instead, while there, he meets Juliet and falls instantly in love with her.
Tybalt sees Romeo, a Montague, at this Capulet feast. Why is a Montague crashing a Capulet feast? Little does he know that Romeo is here not for vengeance but for LOVE 💕 .
Tybalt wants to write a spyOn function to see what Romeo is up to. 👀
Testing and assertion libraries like Jasmine, Mocha, Chai, Sinon etc. have a feature called spies. Spies are funcitons which accept functions as arguments and return modified functions. While the returned function maintians all of the functionality of the original, when returned it is wrapped in new code which allows the spy to keep track of how the function is used: whether it is called, how many times, what arguments it is called with, what it returns, whether it throws errors, etc.
For this REACTO problem, 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
callsfunc
with the given arguments and returns whatfunc
returnsspy
has the following methods:.getCallCount()
: returns the number of timesspy
has been called.wasCalledWith(val)
: returnstrue
ifspy
was ever called withval
, else returnsfalse
.returned(val)
: returnstrue
ifspy
ever returnedval
, else returnsfalse
function romeo(name) {
if(name === 'Juliet') return 'Romeo <3 Juliet';
else return 'Meh'
}
const romeoSpy = spyOn( romeo );
romeoSpy('Juliet'); // 'Romeo <3 Juliet'
romeoSpy.getCallCount() ; // 1
romeoSpy('Rosalina'); // 'Meh'
romeoSpy(3, 5); // returns 'Meh'
romeoSpy.getCallCount(); // 2
romeoSpy.wasCalledWith('Rosalina'); // true
romeoSpy.wasCalledWith('Capulet'); // false
romeoSpy.wasCalledWith(5); //true
romeoSpy.returned('Meh'); // true
romeoSpy.returned('Capulet'); // false
The basic idea here is that we'll create a function to return which closes over some variables private to the scope of spyOn. We can then add some extra methods to that function before it is returned. (Remember that in JS, functions are first-class objects that can have properties and methods just like any other object.) This allows us to call what spyOn returns like a function (because... it is one), but also to use dot syntax to call methods available on the return value.
Here, we use Set to keep track of arguments and return values, but arrays or other structures might also be effective. This approach has the advantage of not using up memory for repeated values.
function spyOn (func) {
let callCount = 0;
const calledWith = new Set();
const returnVals = new Set();
function spy (...args) {
const result = func(...args);
callCount++;
args.forEach(arg => calledWith.add(arg));
returnVals.add(result);
return result;
}
spy.getCallCount = function () {
return callCount;
};
spy.wasCalledWith = function (argument) {
return calledWith.has(argument);
};
spy.returned = function (result) {
return returnVals.has(result);
};
return spy;
}
module.exports = spyOn;
Juliet's cousin Tybalt recognises the Montague boys and forces them to leave just as Romeo and Juliet discover one another.