Testes Unitários 101: Mocks, Stubs, Spies e todas essas palavras difíceis
“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.
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.
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árioPOST /notifications
: Inclui uma nova notificaçãoPUT /notifications/read
: Marca as notificações do usuário como lidasGET /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:
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)
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:
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 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
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.
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 objetoctx
que vimos no arquivoget-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
andafter
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
Vamos escrever nosso primeiro teste então:
Este teste tem muitos pontos importantes:
- Nomeamos o objeto que chamamos de
test
nobeforeEach
deassert
. Isto é apenas para que o código fique mais legível, imagine escrevert.equals
, não é melhorassert.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 comotrue
, 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 comofalse
ounull
, 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
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!