¿Cómo simular funciones en el mismo módulo usando Jest?

Resuelto Mark asked hace 7 años • 11 respuestas

¿Cuál es la mejor manera de burlarse correctamente del siguiente ejemplo?

El problema es que después del tiempo de importación, foomantiene 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.

Mark avatar Jul 15 '17 03:07 Mark
Aceptado

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 bares realmente fácil, porque footambié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.

MostafaR avatar Dec 26 '2017 09:12 MostafaR

El problema parece estar relacionado con cómo espera que se resuelva el alcance de la barra.

Por un lado, module.jsexportas 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 exportscomo 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:

  1. 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');
      });
    });
    
  2. Como dijiste, reescribe la referencia global en el exportscontenedor 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.

John-Philip avatar Jul 24 '2017 19:07 John-Philip