Menu

API de caixa de correio temporária para testes automatizados: criar, consultar, verificar

Use uma API de caixa de correio temporária em testes: crie a caixa via HTTP, consulte o e-mail de confirmação, extraia o código e limpe no CI.

Publicado em

  • automação
  • caixa de correio de teste
  • integração contínua

O e-mail de confirmação é a parte de um fluxo de cadastro que um teste de navegador não consegue conduzir sozinho. Algo precisa possuir um endereço, receber a mensagem e devolver o código ao teste. Uma API de caixa de correio temporária faz exatamente isso: a suíte pede a um serviço que crie uma caixa via HTTP, lê o que chega e descarta a caixa quando o caso termina. Não há navegador, não há caixa compartilhada por pessoas e não há cópia manual.

Este artigo trata do lado da API dessa engrenagem. Não é sobre apontar a aplicação para um endpoint local de recebimento — isso é como capturar e-mail no CI, e os dois resolvem problemas diferentes. A captura local prova o que a sua aplicação envia. A API de caixa de correio prova o que de fato chega, por um caminho real de entrega, a um endereço que o teste controla.

Por que usar uma API de caixa de correio em testes?

Porque a alternativa é uma caixa humana de verdade ou coisa nenhuma.

Uma caixa compartilhada é um fixture ruim. Várias execuções leem a mesma caixa, mensagens de uma execução anterior continuam ali, e o endereço acumula tráfego que nenhum teste pediu. Toda asserção passa a ter de adivinhar qual mensagem pertence ao caso atual, e é na adivinhação que nasce a instabilidade.

Colher a interface web de um provedor com um navegador é a segunda opção ruim. Ela faz o teste depender de marcação que muda sem aviso, de estado de sessão e de um login que a suíte precisa vigiar. No momento em que o provedor muda o estilo de um botão, uma suíte verde fica vermelha por um motivo sem relação alguma com o produto.

A API de caixa de correio elimina os dois problemas. O endereço é criado para o caso, é vazio por construção e é lido por uma interface estável que o teste pode chamar direto. A suíte deixa de se importar com a aparência do provedor e passa a se importar só com a manutenção do contrato: criar, receber, ler, apagar.

Há também um argumento de privacidade. Um endereço criado por API não representa ninguém. Não é a caixa de uma pessoa, não é um lugar onde uma mensagem real poderia cair e é jogado fora com a execução.

De quais endpoints um teste realmente precisa?

Uma API de caixa de correio pode expor dezenas de rotas, mas um cliente de teste precisa de quatro operações, e ajuda nomeá-las do jeito que a suíte vai usá-las.

Criar devolve um endereço e um handle. O endereço é o que se informa à aplicação sob teste como destino. O handle, muitas vezes um token ou identificador, é o que o teste usa para perguntar sobre aquela caixa em todas as chamadas seguintes. A suíte deve tratar o par como um único objeto e nunca reconstruir o handle a partir do endereço, porque os provedores têm liberdade para tornar os dois independentes.

Listar devolve resumos em vez de corpos: uma entrada por mensagem, com identificador, remetente, assunto e horário de chegada. É a chamada que um laço de polling deve usar, porque é barata e basta para responder à única pergunta que importa no início: se algo já chegou.

Ler devolve uma mensagem por inteiro, incluindo as partes de texto e de HTML. É onde o código vive, e é a chamada que só deve acontecer depois que listar informou uma correspondência.

Limpar remove as mensagens da caixa ou apaga a caixa inteira. O teste precisa disso por dois motivos: reiniciar entre tentativas sem criar um endereço novo e limpar quando o caso termina.

Alguns serviços acrescentam um endpoint de espera ou long polling, que segura a conexão até uma mensagem chegar ou um tempo limite passar. É conveniente, mas o cliente ainda deve poder voltar para listar, porque a chamada de espera é a parte mais sujeita a limite de taxa.

Como fazer polling do código sem instabilidade?

O erro mais comum nesse tipo de teste é a espera fixa. Um número constante de segundos é um palpite: curto demais quando a entrega é lenta, longamente desperdiçado quando é rápida, e errado nos dois sentidos numa máquina de CI carregada. Troque-o por um laço que chama listar, verifica uma correspondência e retorna assim que encontra, com um teto que faz o teste falhar em vez de travar o job.

A correspondência é a outra metade do problema. A mensagem que o teste quer é a endereçada ao endereço que o caso criou e, se a caixa puder guardar mais de um tipo de e-mail, a que tem no assunto um fragmento estável. Prefira a correspondência mais recente, para que uma entrega duplicada de uma retentativa não confunda a leitura. Nunca pegue a primeira mensagem sem critério; num endereço reutilizado, é exatamente assim que um código antigo acaba sendo validado.

A extração deve ser ancorada. Um corpo pode conter número de referência, data e preço, e um parser que pega a primeira sequência de dígitos às vezes pega um desses em vez do código. Procure o texto que introduz o código, leia o código na vizinhança dele e, quando nada casar, falhe com o corpo anexado.

Por fim, respeite o limite de reenvio. Um fluxo de código normalmente permite só alguns envios numa janela curta, e esse limite faz parte do comportamento sob teste. Um teste que aperta o botão de novo para obter um código novo acabará recusado e falhará pelo motivo errado. Retente lendo a caixa outra vez, não disparando outra mensagem.

Como integrar a uma suíte de ponta a ponta ou de CI?

A forma limpa é um fixture. Antes de o fluxo começar, o fixture cria uma caixa e devolve o endereço. O teste conduz a aplicação usando esse endereço. Depois que a aplicação confirma que enviou algo, a asserção lê a caixa e extrai o código. Quando o caso termina, o fixture apaga a caixa.

Mantenha o cliente pequeno e injetável. Um módulo embrulha as quatro chamadas; o teste depende desse módulo, nunca de HTTP cru espalhado pela suíte. Isso permite trocar por um dublê nos testes de unidade e apontar a mesma suíte para outro provedor sem reescrever asserções.

No CI, as credenciais ficam no cofre de segredos do job, nunca no repositório e nunca numa linha de log. Dê a cada job ou cada worker paralelo a sua própria caixa, e prefixe os endereços gerados com algo que identifique a execução, para que uma mensagem perdida possa ser atribuída por inspeção. Defina o timeout do cliente abaixo do timeout do próprio job, para que um polling travado falhe com mensagem clara em vez de um cancelamento abrupto.

Retente a leitura, não o fluxo inteiro. Se o código ainda não chegou, espere e leia de novo; refazer o cadastro produziria uma segunda mensagem e, com ela, um segundo candidato para a asserção. E mantenha a API de caixa de correio fora de fluxos de produção: é infraestrutura de teste, e uma suíte nunca deve conseguir enviar a um cliente real por meio dela.

O fluxo em volta do código, e não a mecânica de lê-lo, está em teste do fluxo de verificação de e-mail, e a etapa de parsing é o assunto de OTP em testes de ponta a ponta.

Isolamento e limpeza

Um endereço por caso é a regra que evita a maioria das falhas entre testes. Ela elimina a necessidade de raciocinar sobre qual mensagem pertence a quem e faz a questão da novidade desaparecer, porque a caixa só recebeu o tráfego de um caso.

A limpeza deve ser explícita e incondicional. Apague a caixa num teardown que roda quer o caso passe, quer falhe, não apenas no caminho feliz. Confiar só no tempo de vida do provedor é um erro: a mensagem pode demorar o suficiente para ser lida por uma execução posterior na mesma máquina, e esse tempo de vida é conveniência, não garantia.

Se a limpeza falhar, registre e deixe a suíte terminar. Um erro de limpeza vale a pena saber, mas não é o mesmo que um defeito de produto, e falhar a execução por causa dele ensina a equipe a ignorar falhas de teardown. Trate tudo o que o endereço recebeu como dado de teste: existe para uma asserção, não deve ser exportado nem compartilhado e nunca deve ser tratado como ponto de contato de alguém.

Limites e ressalvas

A API de caixa de correio continua sendo uma dependência de terceiros, e os limites dela viram os seus. Limites por minuto podem recusar uma rajada de criações de uma execução paralela grande. Cotas limitam quantos endereços existem ao mesmo tempo. Mensagens podem atrasar, e uma mensagem atrasada parece exatamente uma mensagem perdida até chegar.

Domínios descartáveis também são amplamente bloqueados. O domínio de um provedor pode ser recusado pelo próprio formulário de cadastro que você está testando, o que transforma um teste legítimo numa falha confusa. Quando isso acontece, a resposta não é abrir exceção para o provedor, mas entender se o produto sob teste rejeita endereços descartáveis de propósito e testar esse comportamento de forma deliberada.

O resumo honesto é que a API de caixa de correio é a ferramenta certa para afirmar o que chega. Para afirmar o que a sua aplicação emite, um endpoint local de recebimento é mais rápido e não tem cota. A maioria das suítes maduras usa os dois: captura local para o grosso das asserções e API de caixa de correio só onde o caminho real de entrega é o objeto do teste.

Próximos passos

Encontre um teste que lê código fazendo polling numa caixa compartilhada e troque-o por um fixture que cria um endereço novo a partir de uma API de caixa de correio. Registre o endereço junto da execução, apague-o no teardown e veja quanto da instabilidade que você vinha tolerando simplesmente deixa de acontecer. Quando precisar de uma caixa real à mão, a página de e-mail temporário cria uma num instante, e os endereços que ela entrega são andaimes para uma execução, nunca uma identidade real.

Continue lendo

Artigos sobre E-mail temporário (descartável / de 10 minutos)