:::: MENU ::::

Testes em Javascript: Diferença entre Fake, Spy, Stub e Mock

Fala galera, beleza? esse artigo vai fazer parte do livro “construindo APIs testáveis com Node.js” então todos os feedbacks são muito bem vindos.
Resolvi escrever sobre a diferença entre fake, spy, stub e mock pois é comum confundir os termos e também não saber qual testDouble usar para cada cenário. Opa, testeDouble? o que é isso?

testDouble

Testar código com ajax, network, timeouts, banco de dados e outras dependências que produzem efeitos colaterais é sempre complicado. Por exemplo, quando se usa ajax, ou qualquer outro tipo de networking, é necessário comunicar com um servidor que irá responder para a requisição; já com o banco de dados será necessário inicializar um serviço para tornar possível o teste da aplicação: limpar e criar tabelas para executar os testes e etc.

Quando as unidades que estão sendo testadas possuem dependências que produzem efeitos colaterais, como os exemplos acima, não temos garantia de que a unidade está sendo testada isoladamente. Isso abre espaço para que o teste quebre por motivos não vinculados a unidade em sí, como por exemplo o serviço de banco não estar disponível ou uma API externa retornar uma resposta diferente da esperada no teste.
Há alguns anos atrás Gerard Meszaros publicou o livro XUnit Test Patterns: Refactoring Test Code e introduziu o termo Test Double (traduzido como “dublê de testes”) que nomeia as diferentes maneiras de substituir dependências. A seguir vamos conhecer os mais comuns test doubles e quais são suas características, prós e contras.

Na prática

Para facilitar a explicação será utilizado o mesmo exemplo para os diferentes tipos de test doubles, também será usada uma biblioteca de suporte chamada Sinon.js que possibilita a utilização de stubs, mocks e spies.
A controller abaixo é uma classe que recebe um banco de dados como dependência no construtor. O método que iremos testar unitariamente dessa classe é o método “getAll”, ele retorna uma consulta do banco de dados com uma lista de usuários.

 

Fake

Durante o teste, é frequente a necessidade de substituir uma dependência para que ela retorne algo específico, independente de como for chamada, com quais parâmetros, quantas vezes, a resposta sempre deve ser a mesma. Nesse momento a melhor escolha são os Fakes. Fakes podem ser classes, objetos ou funções que possuem uma resposta fixa independente da maneira que forem chamadas.

O exemplo abaixo mostra como testar a classe UsersController usando um fake:

 

Nesse caso de teste não é necessária nenhuma biblioteca de suporte, tudo é feito apenas criando um objeto fake para substituir a dependência do banco de dados. O método “findAll” passa a ter uma resposta fixa, que é uma lista com um usuário.
Para validar o teste é necessário verificar se a resposta do método “getAll” do controller responde com uma lista igual a declarada no “expectedDatabaseResponse”.

Vantagens:

  • Simples de escrever
  • Não necessita de bibliotecas de suporte
  • Desacoplado da dependencia original

Desvantagens:

  • Não possibilita testar múltiplos casos
  • Só é possível testar se a saída está como esperado, não é possível validar o comportamento interno da unidade

Quando usar fakes:
Fakes devem ser usados para testar dependências que não possuem muitos comportamentos ou somente para preenchimento de argumentos.

Spy

Como vimos anteriormente os fakes permitem substituir uma dependência por algo customizado mas não possibilitam saber, por exemplo, quantas vezes uma função foi chamada, quais parâmetros ela recebeu e etc. Para isso existem os spies, como o próprio nome já diz, eles gravam informações sobre o comportamento do que está sendo “espionado”.
No exemplo abaixo é adicionado um spy no método “findAll” do Database para verificar se ele está sendo chamado com os parâmetros corretos:

 

Note que é adicionado um spy na função “findAll” do Database, dessa maneira o Sinon devolve uma referência a essa função e também adiciona alguns comportamentos a ela que possibilitam realizar checagens como “sinon.assert.calledWith(findAll, ‘users’)” onde é verificado se a função foi chamada com o parâmetro esperado.

Vantagens:

  • Permite melhor assertividade no teste
  • Permite verificar comportamentos internos
  • Permite integração com dependências reais

Desvantagens:

  • Não permitem alterar o comportamento de uma dependência
  • Não é possível verificar múltiplos comportamentos ao mesmo tempo

Quando usar spies:
Spies podem ser usados sempre que for necessário ter assertividade de uma dependência real ou, como em nosso caso, em um fake. Para casos onde é necessário ter muitos comportamos é provável que stubs e mocks venham melhor a calhar.

Stub

Fakes e spies são simples e substituem uma dependência real com facilidade, como visto anteriormente, porém, quando é necessário representar mais de um cenário para a mesma dependência eles podem não dar conta. Para esse cenário entram na jogada os Stubs. Stubs são spies que conseguem mudar o comportamento dependendo da maneira em que forem chamados, veja o exemplo abaixo:

 

Quando usamos stubs podemos descrever o comportamento esperado, como nessa parte do código:


findAll.withArgs('users').returns(expectedDatabaseResponse)

Quando a função “findAll” for chamada com o parâmetro “users”, retornara a resposta padrão.

Com stubs é possível ter vários comportamentos para a mesma função com base nos parâmetros que são passados, essa é uma das maiores diferenças entre stubs e spies.

Como dito anteriormente, stubs são spies que conseguem alterar o comportamento. É possível notar isso na asserção “sinon.assert.calledWith(findAll, ‘users’)” ela é a mesma asserção do spy anterior. Nesse teste são feitas duas asserções, apenas para mostrar a semelhança com spies, pois múltiplas asserções em um mesmo caso de teste é considerado uma má prática.

Vantagens:

  • Comportamento isolado
  • Diversos comportamentos para uma mesma função
  • Bom para testar código assíncrono

Desvantagens:

  • Assim como spies não é possível fazer múltiplas verificações de comportamento

 

Quando usar stubs:
Stubs são perfeitos para utilizar quando a unidade tem uma dependência complexa, que possui múltiplos comportamentos. Além de serem totalmente isolados os stubs também tem o comportamento de spies o que permite verificar os mais diferentes tipos de comportamento.

Mock

Mocks e stubs são comumente confundidos pois ambos conseguem alterar comportamento e também armazenar informações. Mocks também podem ofuscar a necessidade de usar stubs pois eles podem fazer tudo que stubs fazem. O ponto de grande diferença entre mocks e stubs é sua responsabilidade: stubs tem a responsabilidade de se comportar de uma maneira que possibilite testar diversos caminhos do código, como por exemplo uma resposta de uma requisição http ou uma exceção; Já os mocks substituem uma dependência permitindo a verificação de múltiplos comportamentos ao mesmo tempo.

O exemplo a seguir mostra a classe UsersController sendo testada utilizando Mock:

 

A primeira coisa a se notar no código é a maneira de fazer asserções com Mocks, elas são descritas nessa parte:


"databaseMock.expects('findAll').once().withArgs('users')"

Nela são feitas duas asserções, a primeira para verificar se o método “findAll” foi chamado uma vez e na segunda se ele foi chamado com o argumento “users”, após isso o código é executado e é chamada a função “verify()” do Mock que irá verificar se as expectativas foram atingidas.

Vantagens:

  • Verificação interna de comportamento
  • Diversos asserções ao mesmo tempo

Desvantagens:

  • Diversas asserções ao mesmo tempo podem tornar o teste difícil de entender

Quando usar mocks:
Mocks são úteis quando é necessário verificar múltiplos comportamentos de uma dependência. Isso também pode ser sinal de um design de código mal pensado, onde a unidade tem muita responsabilidade. É necessário ter muito cuidado ao usar Mocks já que eles podem tornar os testes pouco legíveis.

 

Espero que seja util pessoal 🙂

 

Referencias:

[*] https://semaphoreci.com/community/tutorials/best-practices-for-spies-stubs-and-mocks-in-sinon-js

[*] http://martinfowler.com/articles/mocksArentStubs.html

[*] https://www.amazon.com.br/Xunit-Test-Patterns-Refactoring-Code/dp/0131495054


  • Jean Bauer

    Direto ao ponto, muito bom!

    • Waldemar Neto

      Valeu!

  • icaroramiires

    Muito bom Waldemar, estava acompanhando sua série ‘Criando Apis testáveis com Node.js’ é esse post me ajudou ajudou bastante a entender essa parte! Parabéns

    • Waldemar Neto

      Massa cara! que bom que foi util 🙂

  • Massa Waldemar! Parabéns!

    • Waldemar Neto

      Valeu @danjesus:disqus !

  • Muito bom, obrigado por compartilhar!

    • Waldemar Neto

      Opa @mshmeirelles:disqus , obrigado cara!

  • Humm…. muito boa a explicação !!

  • Ficou Show Waldeco!
    Acho que só faltou dizer os outros frameworks/libs que tu estás utilizando… mocha/chai/jasmine ? (provavelmente tu já os tenha apresentado em um momento anterior no livro, mas como artigo “standalone” ficou faltando a apresentação).
    Acho, também, que no primeiro snippet, quando tu define a interface de `Database`, poderias colocar um comentário ao invés de deixar o método vazio, para explicitar que a implementação real deveria retornar algo.
    Mas no geral está excelente.
    O exemplo é de fácil entendimento e a legibilidade do código está sensacional.
    Abraço e toca ficha!

    • Waldemar Neto

      Valeu Cicero!
      Boa vou colocar!