Sinon Spies -- Testing a recursive (and possibly const) function expression - javascript

I am using Mocha with Sinon and attempting to test a recursive call (fibonacci). My code is:
'use strict';
let sinon = require('sinon'),
chai = require('chai'),
expect = chai.expect;
chai.use(require('sinon-chai'));
let fib = function (n) {
if (n === 0) {
return 0;
} else if (n === 1) {
return 1;
} else {
return fib(n-2) + fib(n-1);
}
};
describe('fib', function() {
it('should repeat calculations', function() {
let originalFib = fib;
fib = sinon.spy(fib)
expect(fib(6)).to.equal(8);
expect(fib).to.have.callCount(25);
fib = originalFib;
});
});
This code works as-is, however, if I replace the line:
let fib = function (n) {
with:
const fib = function (n) {
I get the following error:
TypeError: Assignment to constant variable.
This is as expected, but it raises the question, how would I test a recursive function that was declared const using Sinon?
Edited Jasmine has something called .callThrough() which seems to allow testing of recursive function.
It seems to me there is no way to replicate this behavior with Sinon? I have looked at the following bug reports / feature requests:
https://github.com/sinonjs/sinon/issues/668
https://github.com/sinonjs/sinon/issues/989
Thank you.

Calling sinon.spy directly on a function creates a wrapper around the original function that tracks calls and returned values but does not modify it, so you do not need to remember it and restore it.
With that information, the obvious answer would be to simply name your spy something else:
describe('fib', function() {
it('should repeat calculations', function() {
const spy = sinon.spy(fib);
expect(spy(6)).to.equal(8);
expect(spy).to.have.callCount(25);
});
});
This approach works fine for non-recursive functions, but as you might notice, while the first assertion passes, the second fails with only 1 call having been made to spy.
The problem is that there is one more issue at play here. The function fib calls itself directly and those direct recursive calls are not tracked by wrapping the function with sinon.spy.
More details on this issue as well as the corresponding solution is presented in the answer here.

The main issue when you changed the code into const fib = function(n) is because you have this subsequent code fib = sinon.spy(fib). We can't redeclare assignment for any variable with const.
For this kind of test, based on my experience, I feel that using spy is not necessary unless you have other function to call inside fib. We can just execute and check the value for all possible cases.
'use strict';
const sinon = require('sinon'),
chai = require('chai'),
expect = chai.expect;
chai.use(require('sinon-chai'));
const fib = function (n) {
if (n === 0) {
return 0;
} else if (n === 1) {
return 1;
} else {
return fib(n - 2) + fib(n - 1);
}
};
describe('fib', function () {
it('should repeat calculations', function () {
expect(fib(0)).to.equal(0); // add more cases
expect(fib(1)).to.equal(1);
expect(fib(6)).to.equal(8);
});
});
Hope it helps.

Related

Jasmine - How to spy on a deep nested function

I have the following scenario:
file1.js:
async function fctionA(event) {
console.log('In fctionA()!');
let responseA = null;
const formattedEvent = formatEvent(event);
...
const b = await fctionB(formattedEvent);
responseA = someLogicUsing(b);
return responseA; // responseA will depend on 'b'
}
file2.js:
async function fctionB(formattedEvent) {
console.log('Now in fctionB()!');
let resB = null;
...
const c = await fctionC(formattedEvent);
...
resB = someLogicDependingOn(c); // resB will depend on 'c'
return resB;
}
async function fctionC(formattedEvent) {
console.log('AND now in fctionC()!');
let c = await someHttpRequest(formattedEvent);
...
return c;
}
Side notes:
Don't mind formatEvent(), someLogicUsing() orsomeLogicDependingOn() too much. Assume it's just sync logic using provided data)
formattedEvent would be anything depending on the original input event. Just to note functions will use it.
PROBLEM:
What i want to do is to unit test fctionA(), using Jasmine: I want to check the responseA after appying the logic on fctionB(), but mock the result of fctionC().
My (clearly) naive approach was:
file1.spec.js
import * as Handler from '../src/file1';
import * as utils from '..src//file2';
describe('fctionA', () => {
let response = null;
beforeAll(async () => {
const mockedEventA = { mockedInput: 'a' };
const mockedC = { mockedData: 1 };
const expectedResponse = { mockedResponse: 1234 };
spyOn(utils, 'fctionB').and.callThrough());
spyOn(utils, 'fctionC').and.returnValue(Promise.resolve(mockedC));
response = await Handler.fctionA(mockedEventA);
});
it('should return a proper response', () = {
expect(response).toEqual(expectedResponse);
});
});
But after checking logs, i can see that ´fctionC()´ does get executed (when as far as i understood, it shouldn't), therefore does not return the mocked result.
Then, after some try and error, i invoked fctionC() directly in fctionA() (instead of indirectly invoking it through´fctionB()´) just to see what happens, and I can spy it and return a mocked value. fctionC() does not execute (can't see log).
So, that makes me think that, at least the way I'm trying to spy on functions, only work for functions that are directly invoked by the function I'm calling, but not for nested ones-
I'm clearly not an expert in Jasmine, so I can't figure out another option. I looked a lot into docs and posts and blogs but nothing worked for me.
Is there a way to achieve what I try here? I guess I might be doing something really silly or not thinking it through.
Thanks!

Arguments Optional FreeCodeCamp Challenge

in need of help with a codecamp challenge:
Arguments Optional - The challenge
https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/arguments-optional
My Question
I know this can be done with the arguments object (see figure 1), where I can call the function again when the second value is undefined so I've gone for a different approach; However, my code of using currying works but becomes an issue with 'addTogether(5)(7);'
Issue ->
I get the returned value of 12 but doesn't get approved in the code challenge.
I did originally return the value inside the sum function but the challenge required the sum value to be returned in addTogether function, which I did so now it resulting in the following
addTogether(2,3); // now working
addTogether(5)(7); // NOT working
addTogether(23, 30); // now working
Is there something I'm doing wrong that's resulting in the test case failing even though the correct value is returned?
let val = 0;
function sum(a, b) {
val = a + b;
}
function sumTwoAnd(sumFunc) {
return function addTogether(...params) {
let numsArr = [...params];
const res = numsArr.every(el => typeof el === 'number');
if (res === false) return;
if (numsArr.length >= sumFunc.length) {
sumFunc(...numsArr);
} else {
return function(...args2) {
let newArr = numsArr.concat(args2);
addTogether(...newArr);
}
}
console.log(val);
return val;
}
}
let addTogether = sumTwoAnd(sum);
addTogether(2,3);
addTogether(5)(7);
addTogether(23, 30);
Figure 1
Shows how I can get 'test(5)(7)' the second parameter from the function
function test() {
const [f, s] = arguments;
console.log(f, s)
if (s === undefined) {
return s => test(f, s)
}
}
test(23, 30);
test(5)(7);
You declared addTogether using let, so the declaration won't be hoisted above the point where it was defined. This is making your recursive call in the else statement fail, since addTogether() doesn't exist that far up.
You might want to extract the function you're returning in sumTwoAnd() as a separate function definition, so it can freely call itself, similar to your Figure 1 example.
Or you can call sumTwoAnd() instead to regain the function, then pass newArr to said function.
Instead of calling the function I have now returned it...
return addTogether(...newArr);
This now works :)

Execute function conditionally with && syntax JS

Can I execute functions this way?
const test = testFunc() // returns "value" or undefined
test && anotherFunction()
Basically I want to execute anotherFunction() if test const is defined, otherwise just skip it.
I know that I can use something like this:
if (test) {
anotherFunction()
}
but I'm interested why the first method isn't working.
I will appreciate if someone explain me the difference between if () {} and &&.
// I'm confused because if I trying to use the first method I get:
Expected an assignment or function call and instead saw an expression
in my IDE.
The approach will work as expected you can see the below two cases as you mentioned.
First Case - testFunc returns some value
const testFunc = () => 1
const anotherFunction = () => 2
const test = testFunc()
test && anotherFunction()
Second Case - testFunc returns undefined
const testFunc = () => undefined
const anotherFunction = () => 2
const test = testFunc()
test && anotherFunction()
I hope this will help to understand it better.

Call function inside of another function

Using the factory pattern, i wrote snippets of code that use firebase cloud functions :
//Notice that when instantiated it does calculations and returns a value
function sumAndMultiply(int number) {
function sum(i) {
return i + 1;
}
function multiply(i) {
return i * 3;
}
const x = sum(number);
const y = multiply(x);
return y;
}
everting working fine, but, what i can make if i need to unit test multiply() or sum() function?
describe('Unit testing', () => {
it('test sum function', () => {
//How i can call sum function without call sumAndMultiply?
let result = sum(3);
expect(result == 4);
});
it('test multiply function', () => {
//How i can call multiply function without call sumAndMultiply?
let result = multiply(3);
expect(result == 9);
});
});
As I'm talking about unit testing, one idea is to avoid editing the code used for production, but changes that make the code more readable or better are accepted
The problem with your approach is that your code is tightly coupled with the implementation of sum and multiply functions. This makes testing way harder, and testing the units of sum and multiply would take a lot of effort.
One way of solving this code smell is to use Dependency Injection. This way, you would provide both the sum and multiply functions, but the interface of sumAndMultiplyfunctions would need to change. This way, your code would become:
//Notice that when instantiated it does calculations and returns a value
function sumAndMultiply(number, sum, multiply) {
const x = sum(number);
const y = multiply(x);
return y;
}
function sum(i) {
return i + 1;
}
function multiply(i) {
return i * 3;
}
That way, you INJECT these functions. Good thing is that now we can unit test both our sum and multiply functions as you've sugested. Even better, we can now mock our sum and multiply functions on sumAndMultiply.

Implementing Automatic Memoization (returns a closured function) in JavaScript

I've read
http://www.sitepoint.com/implementing-memoization-in-javascript/
Automatic Memoization
In all of the previous examples, the functions were explicitly modified to add memoization. It is also possible to implement a memoization infrastructure without modifying the functions at all. This is useful because it allows the function logic to be implemented separately from the memoization logic. This is done by creating a utility function which takes a function as input and applies memoization to it. The following memoize() function takes a function, “func”, as input. memoize() returns a new function which wraps a caching mechanism around “func”. Note that this function does not handle object arguments. In order to handle objects, a loop is required which would inspect each argument individually and stringify as needed.
function memoize(func) {
var memo = {};
var slice = Array.prototype.slice;
return function() {
var args = slice.call(arguments);
if (args in memo)
return memo[args];
else
return (memo[args] = func.apply(this, args));
}
}
using this, I did
var fib = function(n)
{
if (n <= 1)
{
return 1; // as the Fib definition in Math
}
else
{
return fib(n - 2) + fib(n - 1); // as the Fib definition in Math
}
};
log(memoize(fib)(43));
log(fib(43));
However, I confirmed there's no effect.
I also tried a npm library for the same purpose,
https://github.com/medikoo/memoize
and
var memoize = require('memoizee');
log(memoize(fib)(43));
log(fib(43));
The result, same.
What do I miss, and how to fix and make it work?
Thanks!
EDIT
require('memoizee');
var fib = function(n)
{
if (n <= 1)
{
return 1; // as the Fib definition in Math
}
else
{
return fib(n - 2) + fib(n - 1); // as the Fib definition in Math
}
};
var generator = function(f)
{
return memoize(f);
};
var _fib = generator(fib);
console.log(_fib(40)); //no effect
The memoize call does not alter the fib function, but returns its new, memoized counterpart. In your code, you're calling that one only once, and the original fib function the next time. You need to create one memoized "wrapper", and call that multiple times:
var mFib = memoize(fib);
log(mFib(43));
log(mFib(43));
You could also overwrite the original fib = memoize(fib);, which would have the additional benefit that the recursive calls (which are the interesting ones) will be memoized as well.

Categories

Resources