Testes Unitários 101: Mocks, Stubs, Spies e todas essas palavras difíceis

Lucas Santos
Training Center
19 min readJul 13, 2017

--

“Se você não gosta de testar seu produto, grandes chances de seus clientes também não gostarem.” (Anônimo)

Estamos de volta com mais uma parte da nossa série sobre testes automatizados. Apesar de as partes não serem interligadas, é legal ler a primeira parte e voltar aqui para continuar de onde paramos!

Nesta seção, vamos conversar um pouco sobre testes, em especial os unitários. E vamos entender um pouco sobre o que são essas palavras complicadas como mocks, stubs, spies e etc. Lembrando que teremos mais uma publicação sobre o tema!

O que é uma unidade?

Como o próprio nome já diz, e o texto anterior já deu uma explicada, um teste unitário é um teste de uma unidade do seu sistema. Mas o que é uma unidade do sistema?

Se você está trabalhando com programação orientada a objetos, provavelmente a sua unidade será uma classe, enquanto em programação funcional será (pasmem) uma função, mas é possível que uma unidade do sistema contenha mais do que uma única classe ou uma função. O ideal seria agrupar por domínio, tudo o que está tratando de um mesmo domínio é considerado uma unidade.

Uma unidade pode contar mais de uma classe

Se ficou muito complicado, pense em seu sistema como uma sequencia de módulos (se você programou de maneira correta) que interagem entre si e formam um módulo maior, que por sua vez interage com outros módulos maiores e formam o sistema como um todo. A unidade é o módulo mais simples possível deste sistema. Mas não necessariamente é uma unica classe, como a imagem ao lado.

Isso facilita muito o entendimento dos nossos testes por dois motivos: primeiro porque podemos isolar esta parte do sistema completamente, criando o que chamamos de mocks para replicar o funcionamento de outra parte que não diz respeito ao nosso teste atual. E segundo porque, como é uma unidade, o teste é relativamente mais simples em estrutura.

Como testar uma unidade?

Uma vez que você identificou o que é uma unidade do seu sistema, um teste unitário tem algumas características interessantes, por este motivo é bem visível a distinção entre um teste de unidade e outros tipos de teste. Algumas características dele são:

  • Isolado: O teste de unidade não pode conter dependências externas (bancos de dados, apis e etc)
  • Stateless: Não se pode guardar estados, ou seja, a cada teste todos os recursos que foram utilizados (instancias, mocks e tudo mais) devem ser destruídos completamente e novos devem ser criados
  • Unitário: É um pouco redundante dizer isso, mas um teste de unidade deve apenas testar uma unidade, ou seja, se você começar a instanciar outras unidades já não é mais um teste unitário.
Ferramentas de automação estão aqui para facilitar a vida do desenvolvedor

Existem diversos modos de se testar um software atualmente, dependendo da linguagem que você usa já é possível até ter recursos nativos destas que podem ser utilizados para fazer o teste. Um grande exemplo é a biblioteca assert do Node.js.

Também existem frameworks de teste que provém um ambiente próprio com ferramentas e algumas facilidades para o desenvolvedor.

Vou dar alguns exemplos:

PHP

Node.js

Java

Ruby

NET

E muitos outros por ai… Não existe uma formula sobre “Qual framework usar”, em geral, você pode usar qualquer um que você se sinta mais a vontade. Porém, se você está começando com testes, é uma boa pedida utilizar os que a comunidade está utilizando mais, pois, provavelmente, ele vai ter uma gama maior de documentação.

Mãos à obra

“Falar e fácil. Me mostre o código.” (Linus Torvalds)

Falamos muito sobre a teoria, mas agora vamos a um pouco de prática sobre tudo o que estamos tentando entender aqui.

Para este artigo, vamos utilizar um exemplo real que já tive durante meus anos de desenvolvimento. Vamos testar uma API de notificações feita em Node.js utilizando o framework Koa.js (que dei o nome carinhoso de Alfred), o objetivo desta API é inserir notificações em uma plataforma de gerenciamento de conteúdo usando o MongoDB como camada de persistência (um banco de dados NoSQL). Em suma, isto é a camada de comunicação entre o botão de notificações para o usuário e o nosso back-end. Uma API bastante simples com três rotas:

  • GET /notifications: Obtém as notificações do usuário
  • POST /notifications: Inclui uma nova notificação
  • PUT /notifications/read: Marca as notificações do usuário como lidas
  • GET /status: Retorna o status atual da API

Para testar esta API, utilizei o framework AVA.js que já mencionei acima. Ele não é o mais comum na comunidade, mas é o que eu encontrei maior facilidade em utilizar devido a sua API bastante simples.

O início

Vamos lá! Basicamente este é o nosso código base (vou explicar ele já já):

Temos um arquivo index, que é o ponto de entrada da nossa API:

Arquivo de entrada para a API

Temos alguns pontos interessantes aqui a tratar:

  • Veja que todo o código é bastante desacoplado
  • Estou utilizando o pacote dotenv para carregar variáveis sensíveis como variáveis de ambiente, isso evita que eu tenha informações confidenciais no meu repositório
  • Estou usando o knoblr como ferramenta de logs porque simplesmente acho o winston muito complexo
  • Estou usando o JSDoc em algumas partes importantes do código
  • A maioria dos meus erros vão em classes como a que eu vou mostrar abaixo, apenas porque eu prefiro ter um nome para cada exceção que eu estou enviando ao usuário, isso torna mais fácil o debug lá na frente (resquícios da época do .NET)
Classe base de erros

Depois temos o nosso arquivo principal que é o app.js. Este arquivo é basicamente o responsável por colar todas as peças do software juntas em um único lugar. Veja, um projeto qualquer sempre vai ter uma série de arquivos para modularizar o mesmo, de forma que pode ficar um pouco complicado ficar chamando eles de todas as partes, então temos que incluir um arquivo principal que vai ser o ponto de partida de todo o nosso workflow, ou seja, com a responsabilidade única de juntar todos os arquivos em uma sequencia lógica de execução:

Arquivo principal da API

Sim, ele é um pouco grande, mas por quê? Isto foi feito para que o código fosse muito modularizado, veja que podemos executar todas as partes individuais do mesmo independentemente do servidor que tivermos em execução.

Neste artigo e nesta API em particular, isso não surtirá grandes efeitos na hora de testarmos, porém se estivermos trabalhando com um outro tipo de webserver ou até com outros tipos de sistemas, é importante que possamos separar as partes do nosso código para poder abranger uma code coverage maior (já vamos falar sobre isso).

Pontos importantes:

  • Todas as funções dependem do parâmetro server isso permite que instanciemos nosso testes (se necessário) utilizando uma instancia própria do webserver que estamos utilizando
  • A primeira função retorna uma Promise, e então tiramos vantagem da assincronia do Node para poder carregar a nossa API tranquilamente.

Definindo a unidade

O que é uma unidade? Eis a questão

O primeiro passo de uma sequencia de testes é definir o que é uma unidade no nosso sistema. É importante conversar com seu time sobre o que eles consideram que seria uma unidade, uma vez que o desenvolvedor que criou o software está “viciado” e tende, na maior parte do tempo, a errar neste ponto.

No nosso caso, os arquivos index e app não importam para os testes, porque não queremos testar o comportamento do server do Koa em si, mas sim o comportamento de suas rotas, isso que é importante.

Na maior parte do tempo, quando temos uma API, a unidade será a lógica executada na rota, uma vez que os servidores geralmente são pacotes externos que possuem seus próprios testes e sua própria equipe de desenvolvimento, logo, não precisamos testar novamente.

Agora temos nossa unidade definida, criemos nossos códigos de forma que possamos testá-los individualmente. Teremos sempre rotas neste modelo:

A rota acima basicamente testa se a API está funcionando. A definição de “funcionando” não é só ter o servidor no ar, mas ela precisa também estar conectada no banco de dados, por isso testamos a conexão do mongo com a linha if (driver.connection.readyState === 1) .

Temos uns pontos bastante interessantes:

  • Note que não estou usando o mongoose diretamente, por quê? Pois estas rotas foram criadas originalmente com o intuito de podermos trocar o driver de banco de dados da maneira que quiséssemos (por exemplo, se quiséssemos utilizar o Redis ao invés de Mongo), mas esta decisão foi alterada posteriormente

Veja que, no final do código damos export em um objeto com duas coisas:

  • O objeto de rota que o Koa consegue ler e identificar como uma rota
  • A função que essa rota executa apenas para podermos testar a lógica nos testes

Isto é um código direcionado para testes, pois, normalmente, exportaríamos apenas a rota.

Escrevendo os testes

Agora que definimos a unidade e também já sabemos como funciona o sistema, podemos começar a escrever os testes unitários.

É uma excelente prática começar com a unidade mais simples do teste e ir avançando para as mais complexas. Então, vamos pegar nossa unidade mais simples, que é a rota de status que mostrei acima.

Analisando esta rota, vemos que ela pode retornar duas coisas:

  • Um status 200 quando tudo estiver OK
  • Um status 503 quando o servidor estiver indisponível
  • Um erro quando o objeto ctx for nulo, isto não está explicito no código
O caminho feliz

Agora que sabemos como nossa rota se comporta, podemos definir os nossos testes. Como temos dois estados possíveis, temos que testar ambos para ter a cobertura completa do que ela faz, ou seja, temos que ter um teste para o “caminho feliz”, que é quando está tudo OK, e também precisamos testar se ela de fato está nos dando um 503 quando as coisas ficam estranhas. Vamos sempre começar pelo caminho feliz.

É uma convenção de desenvolvimento que estruturemos nossos testes por arquivos, de forma que cada arquivo vai conter um grupo de testes relacionados a uma unidade, e estes arquivos devem ter a extensão .test.js neste caso (porque estamos usando javascript, se você estiver usando, por exemplo, PHP, use .test.php).

Então, vamos criar uma pasta tests e, dentro dela, um arquivo status.test.js (o teste não precisa ter o mesmo nome do arquivo de rota). Neste arquivo vamos iniciar com um bloco simples seguindo a própria documentação do AVA, começamos importando as dependências que vamos usar neste teste:

import describe from 'ava'
import dotenv from 'dotenv'
import { func } from '../src/routes/get-status'

Estamos importando três coisas:

  • O nosso framework de testes. Nomeei como describe somente porque, na hora de escrevermos os testes, eles ficam mais legíveis (o padrão é test , de acordo com a documentação)
  • Importamos o dotenv , apenas porque a API obrigatoriamente necessita das mesmas para poder funcionar
  • Importamos a função da nossa rota, que é o objeto do teste

Perceba que este teste não tem nenhuma dependência externa, todas as dependências são criadas por nós ou então são configurações de ambiente.

O AVA (e a maioria dos outros frameworks) possuem o que é chamado de before e beforeEach que fazem basicamente o que descrevem: Executam algo antes de todos os testes (uma única vez) e antes de cada teste individual. No geral, não é uma boa prática para testes de unidade utilizar o before a não ser para setar uma configuração global ou alguma coisa que não tenha a ver com o objeto de testes, isto porque esta função cria um estado global que é compartilhado por todos os testes durante seu lifetime de execução, o que mata nosso princípio do stateless.

Mas na nossa API, temos alguns logs, de forma que se executarmos os testes nela, esses logs serão printados na tela e vão poluir a saída do relatório do teste. Precisamos mutar a saída de log do console, como fazemos isso?

Removendo logs de aplicação

É importante que removamos os códigos que exibem mensagens desnecessárias na tela porque isto só vai poluir o relatório final do teste e tudo vai ficar mais complicado.

Estamos usando um pacote de logs chamado knoblr, por ser mais simples e atender totalmente o que eu preciso nessa aplicação (que é apenas mostrar um log colorido e detalhado na tela). Como esta é uma biblioteca minha, eu sei que, por baixo dos panos, o knoblr usa o console.log para printar as mensagens, mas se você estiver usando outro pacote como o Winston.js, provavelmente ele também usa algo do tipo.

Precisamos remover todas as saídas desnecessárias

No Javascript, a API de console é um objeto que interage com o stdout , ou seja, ele manipula uma função do escopo global chamada write para poder escrever tanto erros quando mensagens na sua tela. Basicamente, para silenciar esses outputs, precisamos apenas remover esta função e faze-la nula. Em Node, isto é muito simples:

describe.before((test) => {
process.stderr.write = function () {} // Definimos uma função nula para o output de erros
process.stdout.write = function () {} // O mesmo para o stdout

Veja que usamos o describe , que é o objeto do AVA que renomeamos, e o método before , que leva uma função anônima com um parâmetro test , que é o objeto que representa nosso teste atual. Aqui não interagimos com ele, mas o test vai ser muito importante lá na frente.

Agora já não vamos mais ter saídas desnecessárias.

Criando um contexto

Em testes unitários, não podemos ter estados nem dependências, mas podemos ter contextos. O contexto de um teste é definido como um ambiente local que é mutável e compartilhado por todos os testes dentro da sua suíte (uma suíte de testes é um conjunto de testes da mesma unidade).

Todos os frameworks possuem um objeto de contexto, este objeto é compartilhado em todos os testes dentro do arquivo e servem para transmitir variáveis iguais entre os mesmos de forma que você não precise ficar repetindo código. São muito usados par guardar constantes e valores de teste como nomes de usuários, textos de modelo e etc.

O AVA nos permite interagir com o contexto através do método test.context , onde test é aquele parâmetro que passamos para a arrow function dentro do describe no capítulo anterior. Além disso, o contexto precisa ser zerado e recriado sempre que um novo teste é iniciado, desta forma não corremos o risco de termos estados compartilhados entre os testes.

Para definir nosso contexto vamos utilizar o seguinte código:

describe.beforeEach((test) => {
test.context = {} // Zera o contexto

test.context.routeContext = {} // Nossa própria variável deve ser zerada (isto é só uma precaução)

dotenv.config() // Carregamos as variáveis de ambiente
}

Neste teste só estamos utilizando um contexto, que são os parâmetros que vamos passar para a rota.

É importante destacar que só estamos usando o contexto da rota (definido como routeContext acima) porque o Koa importa um objeto de contexto para a rota (que vou explicar em detalhes depois), que é o objeto ctx que vimos no arquivo get-status.js alguns capítulos antes. Se isto não acontecesse, poderíamos tranquilamente ignorar o contexto.

Veja que também zeramos todo o contexto a cada teste que é executado. É importante dizer que o AVA não permite acesso ao contexto a partir do before, como é dito na documentação:

Context sharing is not available to before and after hooks.

Descrevendo os testes

A facilidade de se desenvolver um teste é proporcional a facilidade de lê-los

Entendeu porque eu nomeei o objeto como describe ? Por convenção, os testes são descritos, assim como a maioria das coisas em programação de software. Grandes bibliotecas de testes usam a palavra describe como uma base para dizer o que um teste faz. Geralmente descrevendo um grupo de testes baseado em um template:

Module: Route
Describe: Status Test
It: Should return 200 when both the database and the API are ok
It: Should return 503 when the database or the API are offline

Isso é muito explorado no framework Mocha.js, que utiliza exatamente esta descrição para definir seus testes:

Veja que podemos escrever nosso template acima da mesma forma:

Da mesma forma vamos utilizar o AVA para deixar isso um pouco mais simples. Nele não precisamos descrever os grupos, o framework vai agrupar cada teste por arquivo, de forma que teremos uma saída parecida com essa

Saída desejada para nosso teste de status (note os três testes que queremos)

Vamos escrever nosso primeiro teste então:

Este teste tem muitos pontos importantes:

  • Nomeamos o objeto que chamamos de test no beforeEach de assert . Isto é apenas para que o código fique mais legível, imagine escrever t.equals , não é melhor assert.equals ? Veja que podemos traduzir o segundo modo para linguagem natural. A facilidade de se desenvolver um teste é proporcional a facilidade de lê-los
  • Estamos utilizando o assert.plan , isto é um recurso exclusivo do AVA e de alguns frameworks. Basicamente, estamos falando “Estou esperando apenas uma verificação neste teste”, evitando que façamos coisas a mais ou a menos, fazendo com que o teste fique muito controlado, no entanto em alguns casos não é uma boa prática.

Asserções

Usamos bibliotecas de asserção para verificar se um valor ou um objeto tem determinadas características que desejamos testar. Podemos verificar a igualdade, a diferença ou até o resultado de um erro. Elas são um meio mais legível de executar uma verificação de valores (ao invés de ter que fazer vários ifs e else’s. Por convenção, essas bibliotecas se chamam assert.

Veja que assert possui um método deepEqual . Este é um método nativo do sistema, ele diz basicamente que o resultado deve ser igual em tipo e valor ao objeto observado, neste caso temos que ter o status igual em texto a 200 e também igual em tipo (numérico) a 200.

Além do deepEqual temos outros modificadores de asserção:

  • Pass: Faz com que o teste passe na hora
  • Fail: Falha o teste instantaneamente
  • Truthy: O valor é verdadeiro léxico, que considera 1 (inteiro) e true (booleano) ambos como verdadeiros. A maioria dos compiladores e interpretadores avaliam 1 como true , isto evita que aconteça uma asserção errada.
  • Falsy: O valor é falso léxico, que considera 0 (inteiro) e false (booleano) ambos como falsos. A maioria dos compiladores e interpretadores avaliam 0 como false ou null, isto evita que aconteça uma asserção errada.
  • False: O valor é o booleano falso, considera apenas o false como falso, ignora o 0 tratando-o como número
  • True: O valor é o booleano verdadeiro, considera apenas o true como verdadeiro, ignora o 1 tratando-o como número
  • Is: O objeto é o mesmo do que o esperado em valor, sem verificação de tipo, o mesmo que x==y
  • Not: O objeto não é o mesmo do que o esperado em valor, sem verificação de tipo, o mesmo que x!=y
  • DeepEqual: O objeto é o mesmo que o esperado tanto em valor quanto em tipo, o mesmo que x===y
  • NotDeepEqual: O objeto não é o mesmo que o esperado tanto em valor quanto em tipo, o mesmo que x!==y
  • Throws: A função envia um erro, ou então uma Promise é rejeitada com um erro, possibilita a verificação do erro
  • NotThrows: A função não envia nenhum erro, ou a Promise não sofre rejeição por erro
  • Regex: Verifica se um valor especificado é válido por uma regex
  • NotRegex: O contrário de Regex
  • IfError: Verifica uma mensagem de erro

Verifique também o conceito de snapshots, ele é muito presente em bibliotecas como o AVA e o Jest (veja aqui e aqui sobre eles).

Também note que caímos no nosso primeiro conceito de testes unitários, o Mock. Usamos um objeto DbMock para simular um banco de dados, mas o que é um Mock de verdade? Antes, vamos ter que falar sobre outras coisas mais simples.

Fingindo ser o que não é

Muitas vezes nossos sistemas não são desacoplados o suficiente para permitir um teste realmente unitário. Tudo bem, isso não é errado, sistemas complexos raramente vão ter desacoplamento suficiente para permitir isso, então utilizamos o que chamamos de Mocks em termo geral.

Um Mock, generalizando, é tudo aquilo que utilizamos como uma cópia de um elemento real, ou seja, é uma “falsificação” de algum objeto que se comporta exatamente como o objeto original do ponto de vista da unidade em teste. Entendeu? Complicado não é? Vamos simplificar.

Digamos que queremos testar uma conexão com o banco de dados, porém nossos testes devem ser desacoplados, como faremos? Podemos imaginar que temos um banco de dados totalmente funcional e criar um objeto que represente ele, que finja ser o banco de dados, mas ele só vai possuir e responder aos métodos que chamamos e que utilizamos, e vamos poder responder da maneira que quisermos. Desta forma podemos testar nossa conexão com o banco de dados, assumindo que o mesmo funciona e retornará um valor previsto por nós (que será o que nossa função testada estará esperando).

Trazendo para a vida real, podemos definir um mock como alguma coisa do tipo:

Você está usando um caixa eletrônico. Quando você quer sacar dinheiro, basta que você aperte alguns botões e o dinheiro vai sair se você tiver disponível, mas você não sabe como ele sai de dentro do caixa eletrônico e, sinceramente, não importa para você. Podemos substituir o interior do caixa por uma cadeira e colocar alguém entregando o dinheiro pela fresta, para os usuários, o caixa continua funcionando, mas agora seu interior é controlado por outra coisa.

Você seria o objeto em teste (o chamado SUT, de Subject Under Testing) , o caixa seria alguma dependência externa (como um DB), podemos trocar as entranhas do caixa por uma outra coisa, desde que ele continue entregando o dinheiro como você espera. Se quisermos que ele falhe, basta dizer a pessoa que não entregue mais o dinheiro. Assim o comportamento fica totalmente previsível.

Os objetos de teste (chamados de test doubles, em inglês) dividem em três categorias principais: os spies, os stubs e os mocks propriamente ditos, embora ainda tenhamos outras duas classes chamadas de Dummies e Fakes.

Hoje, para Javascript, a biblioteca mais famosa para a criação destes tipos de objetos é o Sinon.js.

Cada um destes objetos tem uma característica diferente e pode ser usado em diferentes contextos. Vou dar alguns exemplos e isto vai clarificar um pouco as coisas.

Spies

Muitas vezes, testar o SUT sozinho não é suficiente, precisamos saber se ele chamou alguma função ou quais tipos de argumentos foram passados, por exemplo, a um callback ou a uma promise, para isso, existem os spies.

Spy é uma denominação dada a um objeto que grava suas interações com outros objetos. Eles particularmente úteis para testar callbacks, visto que temos propriedades que assumem valores true, false e outros de acordo com a chamada.

describe('Deve chamar o callback quando der subscribe', (assert) => {     
var callback = sinon.spy();
PubSub.subscribe("message", callback);
PubSub.publishSync("message");
assert.true(callback.called);
})

Veja que estamos fazendo a criação de um spy, que é uma função vazia que apenas grava suas interações como: quem a chamou, quantas vezes, quais argumentos foram passados e etc. Depois podemos verificar cada uma destas propriedades de forma individual com nossas assertions comuns.

Note que podemos verificar a propriedade callback.called que assume true se a função callback foi, de fato, chamado.

Stubs

Um stub é uma evolução de um spy. Em suma, um stub é um objeto com um comportamento fixo e previsível.

Estes objetos são utilizados principalmente em dois casos:

  • Evitar alguma interface desnecessária com alguma dependência (por exemplo uma chamada a uma API externa). Um exemplo disso é o Nock, um pacote para simular chamadas http.
  • Para alimentar o sistema com dados conhecidos. Então podemos forçar um determinado caminho.

O stub pode ser simples o suficiente para ser chamado puramente com Javascript como no exemplo abaixo:

describe('Exemplo de stub puro', (assert) => {     
var task = { completed = true }
})

Ou então ser mais rebuscado, implementando não só o comportamento mas também sobrescrevendo totalmente um método conhecido e, ainda, implementando um spy.

Veja que, no exemplo acima, temos um stub e dois spies. Os spies estão neste exemplo somente para fazer o que fazem de melhor: Uma função sem implementação. Enquanto o stub está com um comportamento previsível, veja que usamos sinon.stub().throws() , isso significa que nosso stub vai lançar um erro, isso é um comportamento pré definido e esperado.

Mocks

Mocks são a evolução dos stubs, pois não substituem um único método, mas sim uma classe toda, mas somente implementa o método escolhido.

Por exemplo, podemos substituir o método de busca do Mongoose pelo nosso próprio método e retornar nosso próprio valor, e ainda por cima implementar o spy nisso tudo.

Note que, com o Sinon, temos outros meios de sobrepor um método, o mostrado acima é um deles, mas também possamos enviar o nosso objeto completo (por exemplo, podemos enviar uma instancia do nosso Mongo) e o Sinon vai criar uma sobreposição do mesmo e implementar a nossa regra acima dele.

Dummies

Dummies são objetos nulos, ou seja, literalmente não fazem nada. Geralmente são utilizados para preencher valores em listas de argumentos.

Este não é um objeto muito importante porque ele pode ser substituído por um valor null qualquer, mas, em alguns casos esses objetos não podem ser nulos puros, e sim devem possuir um tipo específico, neste caso os Dummies entram em cena.

Perceba que o objeto dummyTask é uma função vazia, familiar? Já usamos isso alguns capítulos antes quando silenciamos as saídas de texto.

Fakes

Fake Objects (ou Fakers) são implementações reais e funcionais de alguma dependência, mas de alguma forma são incompletas para serem colocadas em produção (por exemplo, um banco de dados na memória).

Fakers podem ser utilizados quando precisamos incorporar um funcionamento real de um sistema mas precisamos que ele seja controlado.

Podemos implementar uma função XHR (o famoso AJAX) falsa, que tem um comportamento previsível, podemos usar o Faker próprio do Sinon.

Um uso comum são os fake timers, que simulam um objeto de cronômetro ou de data (quem trabalha com Javascript sabe como a API Date é triste), desta forma podemos, por exemplo, inserir um documento no nosso MongoDB com a data atual do sistema e verificar se de fato ela foi inserida, pois este objeto ficará fixado em uma data e não passará o tempo, permitindo a verificação posterior.

Veja que podemos controlar o tick do relógio.

De volta aos testes

Back to the tests

Voltando aos nossos testes, perceba que no último exemplo, utilizamos um stub, e não um mock (veja que nem usamos o Sinon).

Tendo entendido isso, vamos para o segundo teste desta mesma rota de status.

O caminho triste

Já testamos nosso caminho feliz, quando a API retorna 200, agora também temos que testar o nosso caminho triste, ou seja, quando ou o banco ou a API estão fora do ar e ela nos retorna um 503.

Vamos usar a mesma técnica que o teste anterior, mas desta vez vamos trocar nosso readyState para zero:

O resto do teste se mostra exatamente igual, mas veja que não precisamos testar a queda da API, apesar da descrição, se a API estiver fora do ar, independente da nossa chamada a rota nunca vai retornar 503, ao invés disso ela não vai retornar nada porque ela não vai estar no ar.

Dando um tempo

Vamos parar por aqui neste artigo, vou dividi-lo em dois para que não fique tão longo e chato de ler. No próximo capítulo vou abordar o resto dos testes desta API e também ensinar mais algumas palavras complicadas!

Não deixe de acompanhar mais do meu conteúdo no meu blog e se inscreva na newsletter para receber notícias semanais!

--

--

Lucas Santos
Training Center

Brazilian Programmer, caught between the black screen and rock n' roll 🤘 — Senior software Engineer