Por que testar o backend
Um teste automatizado é código que executa o seu código e falha se o comportamento não for o esperado. No backend ele nos protege de quebrar endpoints, regras de negócio ou contratos de dados ao refatorar.
Destacam-se duas grandes famílias:
- Testes unitários (unit): testam uma peça isolada (um serviço, uma função pura, um handler) sem tocar em rede, disco nem banco de dados. São rápidos e precisos: se falham, você sabe exatamente onde está o problema.
- Testes de integração (integration): exercitam várias peças juntas, tipicamente uma requisição HTTP real que percorre rotas, middleware e handlers. São mais lentos, mas verificam que tudo se encaixa de ponta a ponta.
Isolar com dublês de teste (mocks)
Para testar uma unidade sem suas dependências reais usam-se dublês: objetos falsos que substituem uma dependência (o banco de dados, um serviço externo). Um mock ou espião também registra como foi chamado, para poder afirmar sobre isso depois.
// Serviço que depende de um "repositório" (acesso a dados).
function registrarUsuario(repo, nome) {
return repo.salvar({ nome, criado: true });
}
// No teste, passamos a ele um repo FALSO que espiona as chamadas.
const chamadas = [];
const repoMock = {
salvar(usuario) { chamadas.push(usuario); return { id: 1, ...usuario }; },
};
const r = registrarUsuario(repoMock, "Ana");
// Verificamos comportamento (não implementação interna):
// - salvar foi chamado exatamente uma vez
// - com o nome correto
// - e retornamos o que o repo retornou
A chave do mock: comprovar que foi chamado corretamente (quantas vezes, com quais argumentos) sem executar a dependência real.
Testar endpoints com supertest
Para os testes de integração de um servidor HTTP, a biblioteca supertest lança a app em uma porta efêmera e permite escrever asserções sobre a resposta:
import request from "supertest";
import { app } from "./app.js";
await request(app)
.get("/usuarios/1")
.expect(200)
.expect((res) => {
if (res.body.nome !== "Ana") throw new Error("nome incorreto");
});
o supertest se encarrega de iniciar e fechar o servidor; você só descreve a
requisição (.get, .post, .send(...)) e o que espera (.expect(...)).
TDD (Test-Driven Development)
O desenvolvimento guiado por testes inverte a ordem habitual em um ciclo curto:
- Vermelho: escreva primeiro um teste que descreve o comportamento desejado. Falha (o código ainda não existe).
- Verde: escreva o mínimo de código para que o teste passe.
- Refatorar: limpe o código com a rede de segurança do teste em verde.
O TDD incentiva a projetar funções pequenas e testáveis, e deixa uma suíte de testes como subproduto natural.
Exemplos
Um espião simples: registrar as chamadas em um array
function notificar(servico, mensagem) {
servico.enviar(mensagem);
}
const chamadas = [];
const servicoMock = { enviar: (m) => chamadas.push(m) };
notificar(servicoMock, "Olá");
notificar(servicoMock, "Adeus");
console.log(chamadas.length); // 2 -> foi chamado duas vezes
console.log(chamadas[0]); // "Olá" -> com o argumento correto