¿Cómo cambiar la implementación simulada por prueba única?

Resuelto Andrea Carraro asked hace 6 años • 8 respuestas

Me gustaría cambiar la implementación de una dependencia simulada en cada prueba ampliando el comportamiento predeterminado del simulacro y revirtiéndolo a la implementación original cuando se ejecute la siguiente prueba.

Más brevemente, esto es lo que estoy tratando de lograr:

  1. Dependencia simulada
  2. Cambiar/ampliar la implementación simulada en una sola prueba
  3. Volver al simulacro original cuando se ejecute la próxima prueba

Actualmente estoy usando Jest v21. Así es como se vería una prueba típica:

// __mocks__/myModule.js

const myMockedModule = jest.genMockFromModule('../myModule');

myMockedModule.a = jest.fn(() => true);
myMockedModule.b = jest.fn(() => true);

export default myMockedModule;
// __tests__/myTest.js

import myMockedModule from '../myModule';

// Mock myModule
jest.mock('../myModule');

beforeEach(() => {
  jest.clearAllMocks();
});

describe('MyTest', () => {
  it('should test with default mock', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });

  it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
    // Extend change mock
    myMockedModule.a(); // === true
    myMockedModule.b(); // === 'overridden'
    // Restore mock to original implementation with no side effects
  });

  it('should revert back to default myMockedModule mock', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });
});

Esto es lo que he probado hasta ahora:

  1. mockFn.mockImplementationOnce(fn)

    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      myMockedModule.b.mockImplementationOnce(() => 'overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    

    Ventajas

    • Vuelve a la implementación original después de la primera llamada.

    Contras

    • Se rompe si la prueba llama bvarias veces
    • No vuelve a la implementación original hasta que bno se llama (se filtrará en la próxima prueba)
  2. jest.doMock(moduleName, factory, options)

    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      jest.doMock('../myModule', () => {
        return {
          a: jest.fn(() => true,
          b: jest.fn(() => 'overridden',
        }
      });
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    });
    

    Ventajas

    • Se vuelve a burlar explícitamente en cada prueba.

    Contras

    • No se puede definir una implementación simulada predeterminada para todas las pruebas
    • No se puede extender la implementación predeterminada, lo que obliga a volver a declarar cada método simulado
  3. Burlarse manualmente con métodos de establecimiento (como se explica aquí )

    // __mocks__/myModule.js
    
    const myMockedModule = jest.genMockFromModule('../myModule');
    
    let a = true;
    let b = true;
    
    myMockedModule.a = jest.fn(() => a);
    myMockedModule.b = jest.fn(() => b);
    
    myMockedModule.__setA = (value) => { a = value };
    myMockedModule.__setB = (value) => { b = value };
    myMockedModule.__reset = () => {
      a = true;
      b = true;
    };
    export default myMockedModule;
    
    // __tests__/myTest.js
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
      myModule.__setB('overridden');
    
      myModule.a(); // === true
      myModule.b(); // === 'overridden'
    
      myModule.__reset();
    });
    

    Ventajas

    • Control total sobre los resultados simulados

    Contras

    • Mucho código repetitivo
    • Difícil de mantener a largo plazo
  4. jest.spyOn(object, methodName)

    beforeEach(() => {
      jest.clearAllMocks();
      jest.restoreAllMocks();
    });
    
    // Mock myModule
    jest.mock('../myModule');
    
    it('should override myModule.b mock result (and leave the other methods untouched)', () => {
    
      const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
    
      myMockedModule.a(); // === true
      myMockedModule.b(); // === 'overridden'
    
      // How to get back to original mocked value?
    });
    

    Contras

    • No puedo volver mockImplementational valor de retorno simulado original, lo que afecta las próximas pruebas.
Andrea Carraro avatar Feb 14 '18 22:02 Andrea Carraro
Aceptado

Utilice mockFn.mockImplementation(fn) .

import { funcToMock } from './somewhere';
jest.mock('./somewhere');

beforeEach(() => {
  funcToMock.mockImplementation(() => { /* default implementation */ });
  // (funcToMock as jest.Mock)... in TS
});

test('case that needs a different implementation of funcToMock', () => {
  funcToMock.mockImplementation(() => { /* implementation specific to this test */ });
  // (funcToMock as jest.Mock)... in TS

  // ...
});
A Jar of Clay avatar Oct 25 '2018 17:10 A Jar of Clay

Un buen patrón para escribir pruebas es crear una función de configuración de fábrica que devuelva los datos necesarios para probar el módulo actual.

A continuación se muestra un código de muestra que sigue el segundo ejemplo, aunque permite proporcionar valores predeterminados y anulados de forma reutilizable.


const spyReturns = returnValue => jest.fn(() => returnValue);

describe("scenario", () => {
  beforeEach(() => {
    jest.resetModules();
  });

  const setup = (mockOverrides) => {
    const mockedFunctions =  {
      a: spyReturns(true),
      b: spyReturns(true),
      ...mockOverrides
    }
    jest.doMock('../myModule', () => mockedFunctions)
    return {
      mockedModule: require('../myModule')
    }
  }

  it("should return true for module a", () => {
    const { mockedModule } = setup();
    expect(mockedModule.a()).toEqual(true)
  });

  it("should return override for module a", () => {
    const EXPECTED_VALUE = "override"
    const { mockedModule } = setup({ a: spyReturns(EXPECTED_VALUE)});
    expect(mockedModule.a()).toEqual(EXPECTED_VALUE)
  });
});

Es importante decir que debes restablecer los módulos que se han almacenado en caché usando jest.resetModules(). Esto se puede hacer en beforeEachuna función de desmontaje similar.

Consulte la documentación del objeto broma para obtener más información: https://jestjs.io/docs/jest-object .

user1095118 avatar Feb 22 '2018 11:02 user1095118

Un poco tarde para la fiesta, pero si alguien más tiene problemas con esto.

Usamos TypeScript, ES6 y babel para el desarrollo nativo de reacción.

Por lo general, nos burlamos de los módulos NPM externos en el __mocks__directorio raíz.

Quería anular una función específica de un módulo en la clase Auth de aws-amplify para una prueba específica.

    import { Auth } from 'aws-amplify';
    import GetJwtToken from './GetJwtToken';
    ...
    it('When idToken should return "123"', async () => {
      const spy = jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
        getIdToken: () => ({
          getJwtToken: () => '123',
        }),
      }));

      const result = await GetJwtToken();
      expect(result).toBe('123');
      spy.mockRestore();
    });

Esencial: https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2

Tutorial: https://medium.com/p/b4ac52a005d#19c5

Thomas Hagström avatar Jan 25 '2019 09:01 Thomas Hagström