Solución:
Los espías en JavaScript dependen de que la función sea propiedad de un objeto. Funcionan reemplazando la propiedad del objeto con una nueva función que envuelve y rastrea las llamadas al original.
Si una función recursiva se llama a sí misma directamente, no es posible espiar esas llamadas, ya que se refieren directamente a la función.
Para espiar llamadas recursivas, deben hacer referencia a funciones que se pueden espiar. Afortunadamente, esto es posible y se puede hacer de dos maneras.
La primera solución es envolver la función recursiva en un objeto y hacer referencia a la propiedad del objeto para la recursividad:
fib.js
const wrappingObject = {
memoization: (num, hash = { '0':0, '1':1 }) => {
if (hash[num-1] === undefined) {
hash[num-1] = wrappingObject.memoization(num-1, hash);
}
return hash[num-1] + hash[num-2];
}
};
export default wrappingObject;
fib.test.js
import fib from './fib';
describe('memoization', () => {
it('should memoize correctly', () => {
const mock = jest.spyOn(fib, 'memoization');
const result = fib.memoization(50);
expect(result).toBe(12586269025);
expect(mock).toHaveBeenCalledTimes(49);
mock.mockRestore();
});
});
La segunda solución es importar una función recursiva de nuevo a su propio módulo y usar la función importada para la recursividad:
fib.js
import * as fib from './fib'; // <= import the module into itself
export function memoization(num, hash = { '0':0, '1':1 }) {
if (hash[num-1] === undefined) {
hash[num-1] = fib.memoization(num-1, hash); // <= call memoization using the module
}
return hash[num-1] + hash[num-2];
}
fib.test.js
import * as fib from './fib';
describe('memoization', () => {
it('should memoize correctly', () => {
const mock = jest.spyOn(fib, 'memoization');
const result = fib.memoization(50);
expect(result).toBe(12586269025);
expect(mock).toHaveBeenCalledTimes(49);
mock.mockRestore();
});
});
Las pruebas anteriores utilizan Jest, pero las ideas se extienden a otros marcos de prueba. Por ejemplo, aquí está la prueba para la segunda solución usando Jasmine:
// ---- fib.test.js ----
import * as fib from './fib';
describe('memoization', () => {
it('should memoize correctly', () => {
const spy = spyOn(fib, 'memoization').and.callThrough();
const result = fib.memoization(50);
expect(result).toBe(12586269025);
expect(spy.calls.count()).toBe(49);
});
});
(Optimicé la memorización para requerir el número mínimo de llamadas)