4
respostas

[Dúvida] Não entendi o teste da chamada simulada ao BD - Aula simulação (mock) de funções

No último exemplo de testes da aula "Simulação (mock) de funções", foi realizado o seguinte teste:

it('Deve fazer uma chamada simulada ao BD', () => {
    const editora = new Editora(objetoEditora);

    editora.salvar = jest.fn().mockReturnValue({
      id: 10,
      nome: 'CDC',
      cidade: 'Sao Paulo',
      email: 'c@c.com',
      created_at: '2022-10-01',
      updated_at: '2022-10-01',
    });

    const retorno = editora.salvar();

    expect(retorno).toEqual(
      expect.objectContaining({
        id: expect.any(Number),
        ...objetoEditora,
        created_at: expect.any(String),
        updated_at: expect.any(String),
      }),
    );
  });
});

Entretanto, não entendi como esse trecho estará testando o método salvar da classe Editora, pois o resultado desse teste sempre será o mesmo, independente do que eu faça no método salvar real.

Entendo que a ideia é criar um mock para que, quando chamado, o método não acione o banco de dados, mas ao menos espero que o teste tenha alguma utilidade para evitar que eu faça alterações indevidas no método salvar. Por exemplo, se eu alterar o método salvar da classe Editora para retornar o número inteiro 1, o teste vai continuar passando.

4 respostas

Oii Mike, tudo bem?

O principal objetivo de utilizar mocks em testes é isolar a unidade de código que tá sendo testada, evitando dependências externas como chamadas ao banco de dados. No seu exemplo, ao usar jest.fn().mockReturnValue(...), você tá criando uma simulação do método salvar que retorna um valor específico quando chamado, sem realmente acessar o banco de dados.

Você mencionou que o teste vai passar independentemente do que o método salvar real faça. Isso é verdade, porque o mock substitui a implementação real do método. Mas, essa prática é útil em alguns cenários:

  1. Isolamento de Testes: Ao usar mocks, você garante que o teste do método salvar não será afetado por problemas no banco de dados ou em outras partes do sistema. Isso ajuda a identificar mais facilmente onde tá o problema caso um teste falhe.

  2. Velocidade: Testes que não acessam o banco de dados são muito mais rápidos, o que é importante quando você tem uma grande suíte de testes.

  3. Previsibilidade: Você pode prever exatamente o que o método salvar deve retornar, facilitando a escrita de testes que verificam o comportamento de outras partes do código que dependem desse método.

Pra garantir que o método salvar real esteja funcionando corretamente, você também deve escrever testes de integração que não utilizem mocks e que verifiquem se o método interage corretamente com o banco de dados.

Espero ter ajudado.

Um abraço e bons estudos.

Oi Lorena, obrigado pela resposta. Espero ter entendido os pontos de utilidade dos mocks de forma geral, mas minha maior dificuldade está em entender a utilidade desse teste específico dessa questão. Por mais que para testar o funcionamento correto do método salvar deva ser feito um teste de integração, ao meu ver o teste unitário também precisaria testar alguma coisa relacionada ao trecho que pode falhar.

Pensemos numa simplificação do exemplo para:

it('Deve fazer uma chamada simulada ao BD', () => {
    const editora = new Editora(objetoEditora);
    editora.salvar = jest.fn().mockReturnValue(1);
    const retorno = editora.salvar();
    expect(retorno).toEqual(1);
});

Em que situação esse teste falharia?

Oii Mike,

  • Seu exemplo de teste:
it('Deve fazer uma chamada simulada ao BD', () => {
  const editora = new Editora(objetoEditora);

  editora.salvar = jest.fn().mockReturnValue({
    id: 10,
    nome: 'CDC',
    cidade: 'Sao Paulo',
    email: 'c@c.com',
    created_at: '2022-10-01',
    updated_at: '2022-10-01',
  });

  const retorno = editora.salvar();

  expect(retorno).toEqual(
    expect.objectContaining({
      id: expect.any(Number),
      ...objetoEditora,
      created_at: expect.any(String),
      updated_at: expect.any(String),
    }),
  );
});
  • Entendimento do teste:

Esse teste tá verificando se o método salvar da classe Editora retorna um objeto com a estrutura esperada. No contexto do teste unitário com mocks, você não tá testando a implementação interna do método salvar, mas sim como ele seria usado por outras partes do sistema.

  • Situações em que o teste seria útil
  1. Verificar a Interação: Você quer garantir que, quando salvar é chamado, ele retorna um objeto com a estrutura esperada. Isso é útil para validar a interface do método, não sua implementação.

  2. Isolar Problemas: Se em outra parte do código você tem um bug que depende do retorno de salvar, usar mocks ajuda a isolar onde o problema pode estar — na lógica que usa salvar ou em salvar em si.

Quando o Teste Falharia

O seu exemplo simplificado:

it('Deve fazer uma chamada simulada ao BD', () => {
  const editora = new Editora(objetoEditora);
  editora.salvar = jest.fn().mockReturnValue(1);
  const retorno = editora.salvar();
  expect(retorno).toEqual(1);
});

Nesse caso, o teste só falharia se a função mock (jest.fn().mockReturnValue(1)) não fosse chamada corretamente ou se o valor retornado pelo mock não fosse igual a 1.

O teste em si não verifica a lógica interna de salvar, mas garante que a função salvar quando chamada (mesmo sendo uma simulação), retorna o valor esperado. É um teste de contrato: verifica que a função é chamada e retorna o que deveria, baseado na simulação.

Então resumindo, o uso de mocks no seu teste é para garantir que o método é chamado corretamente e retorna a estrutura esperada. Pra testar a lógica real de salvar, testes de integração são necessários. Dessa forma, você cobre tanto a interface quanto a implementação do método.

Espero que isso esclareça suas dúvidas!

Um abraço e bons estudos.

Oii Lorena, obrigado pelo comentário. Como falei, entendi a necessidade do uso de mocks no contexto dos testes unitários, mas no exemplo específico que coloquei aqui continua não fazendo muito sentido para mim.

O que entendi até o momento é que o trecho que está sendo checado no teste automatizado tem que ser um trecho que possa falhar para que possamos detectar possíveis erros. E nesse exemplo, é como se fosse um teste que desse um resultado falso positivo, pois o teste vai passar sempre, não importa o que aconteça, por isso meu questionamento.

Além disso, a ideia de testar somente o contrato para esse exemplo específico também não faz muito sentido para mim. Por exemplo, caso o método salvar real não retornasse nenhum valor, esse mock faria com que o teste continuasse funcionando, mesmo sem obedecer o contrato.

Você comentou que para testar a lógica real da função o mais indicado seria um teste de integração, e entendo, ainda mais que não escrevi aqui o conteúdo interno do método salvar. Falo isso porque vendo o conteúdo, dá pra ver que é possível testar o funcionamento de salvar por meio de um teste unitário utilizando mocks para as dependências da função salvar. Dessa forma, mockando as depedências do método salvar em vez do próprio método salvar, existiria a possibilidade de erros.

Por exemplo, considerando a classe Editora da atividade de onde tirei esse teste:

class Editora {
    id;
    
    criar() {
      // código interno da função
    }
    atualizar(id) {
      // código interno da função
    }
    
    async salvar() {
      if (this.id) {
        return this.atualizar(this.id);
      }
      return this.criar();
    }
}

Imagino testes unitários onde sejam criados mocks para os métodos atualizar e criar. E ao chamar o método salvar com um objeto Editora:

  • Sem id, checar se o método criar foi chamado;
  • Com id, checar se o método atualizar foi chamado com o mesmo id.