Como alterar a implementação simulada em um único teste [Jestjs]

86

Eu gostaria de alterar a implementação de uma dependência zombado em um por base um único teste por estender a simulação padrão de comportamento e revertendo-lo de volta para a implementação original quando os próximos executa testes.

Mais resumidamente, isso é o que estou tentando alcançar:

  1. dependência simulada
  2. alterar / estender a implementação simulada em um único teste
  3. reverter para a simulação original quando o próximo teste for executado

Atualmente estou usando Jest v21.

Aqui está a aparência de um teste de Jest típico:

__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
  });
});

Aqui está o que tentei até agora:


1 - mockFn.mockImplementationOnce (fn)

prós

  • Reverte para a implementação original após a primeira chamada

contras

  • Ele quebra se o teste chamar bvárias vezes
  • Ele não reverte para a implementação original até que bnão seja chamado (vazando no próximo teste)

código:

it('should override myModule.b mock result (and leave the other methods untouched)', () => {

  myMockedModule.b.mockImplementationOnce(() => 'overridden');

  myModule.a(); // === true
  myModule.b(); // === 'overridden'
});

2 - jest.doMock (moduleName, factory, options)

prós

  • Mocks explicitamente em cada teste

contras

  • Não é possível definir a implementação simulada padrão para todos os testes
  • Não é possível estender a implementação padrão, forçando a declarar novamente cada método simulado

código:

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'
});

3 - Simulação manual com métodos setter (conforme explicado aqui )

prós

  • Controle total sobre resultados simulados

contras

  • Lote de código clichê
  • Difícil de manter a longo prazo

código:

__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();
});

4 - jest.spyOn (objeto, methodName)

contras

  • Não consigo reverter mockImplementationpara o valor de retorno simulado original, afetando os próximos testes

código:

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?
});
Andrea Carraro
fonte
Agradável. Mas como você faz a opção 2 para um módulo npm como '@ private-repo / module'? A maioria dos exemplos que vejo tem caminhos relativos? Isso funciona para módulos instalados também?
mrbinky3000

Respostas:

47

Um bom padrão para escrever teste é criar uma função de configuração de fábrica que retorna os dados necessários para testar o módulo atual.

Abaixo está algum código de amostra após seu segundo exemplo, embora permita o fornecimento de valores padrão e de substituição de uma forma reutilizável.

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

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

  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)
  });
});
user1095118
fonte
40

Vanilla JS

Use mockFn.mockImplementation (fn) .

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

beforeEach(() => {
  funcToMock.mockImplementation(() => { /* default implementation */ });
});

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

TypeScript

Para evitar que a mensagem mockImplementation não seja uma propriedade de funcToMock , você precisará especificar o tipo, por exemplo, alterando a linha superior acima para a seguinte:

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

Uma questão que aborda esse problema pode ser encontrada aqui: jest typecript propriedade mock não existe no tipo

Um pote de barro
fonte
21

Um pouco tarde para a festa, mas se alguém estiver tendo problemas com isso.

Usamos TypeScript, ES6 e babel para desenvolvimento reagente nativo.

Normalmente simulamos módulos NPM externos no __mocks__diretório raiz .

Eu queria substituir uma função específica de um módulo na classe Auth do aws-amplify para um teste específico.

    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();
    });

Síntese: https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2

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

Thomas Hagström
fonte