¿Cómo simular funciones en el mismo módulo usando Jest?
¿Cuál es la mejor manera de burlarse correctamente del siguiente ejemplo?
El problema es que después del tiempo de importación, foo
mantiene la referencia al original sin burlar bar
.
module.js
:
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
module.test.js
:
import * as module from '../src/module';
describe('module', () => {
let barSpy;
beforeEach(() => {
barSpy = jest.spyOn(
module,
'bar'
).mockImplementation(jest.fn());
});
afterEach(() => {
barSpy.mockRestore();
});
it('foo', () => {
console.log(jest.isMockFunction(module.bar)); // outputs true
module.bar.mockReturnValue('fake bar');
console.log(module.bar()); // outputs 'fake bar';
expect(module.foo()).toEqual('I am foo. bar is fake bar');
/**
* does not work! we get the following:
*
* Expected value to equal:
* "I am foo. bar is fake bar"
* Received:
* "I am foo. bar is bar"
*/
});
});
Podría cambiar:
export function foo () {
return `I am foo. bar is ${bar()}`;
}
a:
export function foo () {
return `I am foo. bar is ${exports.bar()}`;
}
pero en mi opinión esto es bastante feo de hacer en todas partes.
Una solución alternativa puede ser importar el módulo a su propio archivo de código y utilizar la instancia importada de todas las entidades exportadas. Como esto:
import * as thisModule from './module';
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${thisModule.bar()}`;
}
Ahora burlarse bar
es realmente fácil, porque foo
también se usa la instancia exportada de bar
:
import * as module from '../src/module';
describe('module', () => {
it('foo', () => {
spyOn(module, 'bar').and.returnValue('fake bar');
expect(module.foo()).toEqual('I am foo. bar is fake bar');
});
});
Importar el módulo en su propio código parece extraño, pero debido al soporte de ES6 para importaciones cíclicas, funciona sin problemas.
El problema parece estar relacionado con cómo espera que se resuelva el alcance de la barra.
Por un lado, module.js
exportas dos funciones (en lugar de un objeto que contenga estas dos funciones). Debido a la forma en que se exportan los módulos, la referencia al contenedor de los elementos exportados es exports
como usted lo mencionó.
Por otro lado, maneja su exportación (a la que le puso un alias module
) como un objeto que contiene estas funciones e intenta reemplazar una de sus funciones (la barra de funciones).
Si observa detenidamente su implementación de foo, en realidad tiene una referencia fija a la función de barra.
Cuando cree que reemplazó la función de barra por una nueva, en realidad simplemente reemplazó la copia de referencia en el alcance de su module.test.js.
Para hacer que foo use otra versión de bar, tienes dos posibilidades:
En module.js, exporte una clase o una instancia, manteniendo tanto el método foo como el bar:
Módulo.js:
export class MyModule { function bar () { return 'bar'; } function foo () { return `I am foo. bar is ${this.bar()}`; } }
Tenga en cuenta el uso de esta palabra clave en el método foo.
Módulo.test.js:
import { MyModule } from '../src/module' describe('MyModule', () => { //System under test : const sut:MyModule = new MyModule(); let barSpy; beforeEach(() => { barSpy = jest.spyOn( sut, 'bar' ).mockImplementation(jest.fn()); }); afterEach(() => { barSpy.mockRestore(); }); it('foo', () => { sut.bar.mockReturnValue('fake bar'); expect(sut.foo()).toEqual('I am foo. bar is fake bar'); }); });
Como dijiste, reescribe la referencia global en el
exports
contenedor global. Esta no es una forma recomendada de hacerlo, ya que posiblemente introducirá comportamientos extraños en otras pruebas si no restablece correctamente las exportaciones a su estado inicial.