Testes Unitários em JavaScript com Jest: Guia Básico
Introdução: A Essência dos Testes Unitários e Por Que Jest?
Jornada Jest concluída: testes unitários, do básico ao avançado (mocks, snapshots). Você domina a testabilidade. Testes são o alicerce de software robusto; permitem refatorar e inovar com segurança, garantindo qualidade e longevidade. Maestria exige prática contínua: teste é sua superpotência. Explore integração e E2E, aprimore-se e construa um futuro sólido.
Configurando Seu Ambiente: Jest em Minutos
Testes unitários são cruciais à qualidade do software, validando pequenas unidades de código isoladamente. Benefícios: confiança, refatoração segura, detecção precoce e barata de bugs. Ausência causa regressões e débito técnico.
Para JavaScript, Jest (Facebook/React) é o framework padrão. Ele oferece "zero-config", velocidade (paralela) e um ecossistema completo (runner, assertions, mocking, cobertura).
Este guia cobrirá Jest do básico (setup, sintaxe) ao avançado (async, mocking, snapshots), para código JS robusto. Começaremos configurando Jest via "zero-config" para testar rápido.
Os Pilares do Teste: describe, test/it e expect com Matchers
Este guia rápido o levará do zero ao seu primeiro teste Jest.
Pré-requisitos
Node.js e npm (ou Yarn) são necessários. Verifique:
node -v
npm -v # ou yarn -v
Instalação e Configuração
- Inicie um projeto Node.js:
mkdir meu-projeto-jest && cd meu-projeto-jest npm init -y # ou yarn init -y - Instale Jest:
npm install --save-dev jest # ou yarn add --dev jest - Adicione
testscript aopackage.json:{ "scripts": { "test": "jest" } }
Estrutura de Testes
Jest busca .test.js (ou .spec.js) ou pastas __tests__.
Seu Primeiro Teste
src/sum.js:function sum(a, b) { return a + b; } module.exports = sum;src/sum.test.js:const sum = require('./sum'); test('soma 1 + 2 para ser 3', () => { expect(sum(1, 2)).toBe(3); });- Execute:
npm test # ou yarn test
Lidando com o Tempo: Testes Assíncronos com Jest
A função expect() e seus matchers são o cerne dos testes Jest, verificando o comportamento do código. expect(valor) recebe o que testar, e o matcher realiza a comparação.
Matchers Essenciais
toBe(): Igualdade estrita (===) para primitivos.toEqual(): Igualdade de conteúdo para objetos/arrays (recursivo)..not: Inverte qualquer asserção (ex:not.toBe()).toBeNull(),toBeUndefined(),toBeTruthy(),toBeFalsy(): Verificam estados específicos.toContain(): Checa presença em array ou substring.toHaveLength(): Verifica tamanho de array ou string.toMatch(): Compara strings via expressão regular.
test('exemplos de matchers', () => {
expect(2 + 2).toBe(4);
expect({a:1}).toEqual({a:1});
expect(5).not.toBe(3);
expect(null).toBeNull();
expect([1,2]).toContain(2);
expect('abc').toHaveLength(3);
expect('[email protected]').toMatch(/@/);
});
Próximo: testes assíncronos.
O Poder da Simulação: Mocks e Spies para Isolar o Código
Testar código assíncrono (APIs, timers) em JavaScript moderno é um desafio. Por padrão, Jest executa testes síncronos, não aguardando operações assíncronas (ex: setTimeout, Promises), o que causa falhas ou falsos positivos.
Como o Jest Lida com Assincronismo
-
Callbacks (
done()): Se um teste recebedonecomo argumento, Jest aguarda sua chamada para finalizar. Essencial para callbacks assíncronos, sedone()não for chamado, o teste falha por timeout. -
Promises (
.then(),expect.resolves,expect.rejects): Ao retornar uma Promise no teste, Jest aguarda sua resolução ou rejeição. As asserçõesexpect.resolveseexpect.rejectssão as formas preferenciais para testar Promises diretamente, sendo mais claras que.then()/.catch(). Sempre retorne a Promise. -
async/await(Preferencial): A forma moderna, mais legível e robusta. Marque a função de teste comoasync. Useawaitpara esperar a resolução de Promises. Para rejeições,await expect(promessa).rejects.toBe('erro')é a melhor prática, outry/catch(mais verboso).
Melhor Prática: A combinação de async/await com expect.resolves e expect.rejects é a maneira mais recomendada, oferecendo clareza e concisão.
Dominar estas técnicas é vital para testar código assíncrono. Para isolar dependências externas, o próximo passo são os mocks e spies.
Além do Básico: Matchers Avançados e Personalizados
Para testes isolados, simular dependências externas é crucial. Mocks e spies do Jest são vitais para testes rápidos e focados na lógica.
- Stub: Objeto com respostas pré-programadas.
- Mock: Um stub que registra interações para verificação de comportamento.
jest.fn()cria mocks customizáveis para controlar retornos (mockReturnValue), lógica (mockImplementation) e Promises (mockResolvedValue). - Spy: Monitora uma função existente sem alterar sua lógica original.
jest.spyOn()cria spies;mockRestore()reverte alterações.
jest.mock() simula módulos inteiros, definindo seus exports mockados para isolamento.
Para limpeza e gerenciamento de estado entre testes:
mockClear(): Limpa o histórico de chamadas.mockReset(): Limpa chamadas e a implementação do mock.mockRestore(): (Para spies) Restaura a função original espionada.
É comum usarbeforeEach/afterEachpara automatizar essa limpeza.
Dominar mocks e spies do Jest é essencial para testes unitários eficazes e confiáveis.
Gerenciando o Ciclo de Vida: Hooks de Setup e Teardown
Para cenários complexos, Jest oferece matchers avançados e a flexibilidade para criar os seus próprios, elevando a expressividade e precisão das validações.
Matchers para Mocks e Spies
Verifique a interação de funções mockadas:
toHaveBeenCalled(): Chamada ao menos uma vez.toHaveBeenCalledTimes(n): Chamadanvezes.toHaveBeenCalledWith(...args): Chamada com argumentos específicos.
// Ex: expect(mockFuncao).toHaveBeenCalled();
Testando Erros e Exceções
Garanta o tratamento correto de erros:
toThrow()/toThrowError(): Verifica se uma função lança erro (qualquer, mensagem, tipo, regex). Envolva a chamada em uma função anônima:expect(() => func()).toThrow().
// Ex: expect(() => dividir(10,0)).toThrow('erro');
Testes de Snapshot (toMatchSnapshot())
Garanta consistência de UI/dados:
- Salva a primeira saída serializada e compara em execuções futuras. Falha indica mudança; atualize (
u) se intencional. Ideal para estruturas complexas.
// Ex: expect(componente).toMatchSnapshot();
Matchers Flexíveis
Para validações menos exatas:
expect.anything(): Nãonullouundefined.expect.any(Constructor): Instância deConstructor(ex:Date,String).expect.arrayContaining(array): Array real contém elementos doarrayesperado.expect.objectContaining(object): Objeto real contém propriedades e valores doobjectesperado.
Criando Custom Matchers
Aumente a legibilidade e manutenibilidade para lógica complexa/repetitiva:
- Defina a função do matcher com
expect.extend({ seuMatcher(received, arg) { /* lógica */ return { pass, message } } }). - Configure
setupFilesAfterEnvemjest.config.jspara carregar o arquivo dos matchers. - Use em testes:
expect(valor).seuMatcher(arg).
// Ex: expect(10).toBeDivisibleBy(2);
Próximo: Ganchos beforeEach/afterEach otimizam a organização e garantem estado limpo em testes.
Garantindo a Qualidade: Análise de Cobertura de Teste
Testes unitários exigem isolamento e evitam duplicação. Jest oferece hooks de ciclo de vida para testes limpos, independentes e confiáveis.
Hooks de Ciclo de Vida Jest
beforeAll()/afterAll() gerenciam setup/teardown uma única vez por grupo (describe ou arquivo). beforeEach()/afterEach() executam antes/depois de cada teste, garantindo um estado limpo e isolamento. Hooks aninhados seguem uma ordem de execução hierárquica. Dominar esses hooks é essencial para testes robustos e de fácil depuração.
Escrevendo Testes Excelentes: Dicas e Melhores Práticas
A cobertura de teste mede a % do código executado pelos testes. Tipos chave: Linha, Branch, Função.
Jest gera relatórios com --coverage. Saídas: resumo no terminal, HTML interativo (verde = coberto, vermelho = não coberto) em coverage/lcov-report, JSON/XML.
Interprete relatórios HTML: identifique lacunas (linhas vermelhas) para priorizar testes.
Jest impõe limiares mínimos de cobertura via coverageThreshold em package.json/jest.config.js. Falha se limites não forem atingidos. collectCoverageFrom define arquivos.
// package.json ou jest.config.js
{
"jest": {
"coverageThreshold": {
"global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 }
},
"collectCoverageFrom": ["src/**/*.{js,jsx,ts,tsx}"]
}
}
Limitações: Alta cobertura não garante qualidade ou testes eficazes. Indica o que rodou, não como. Foque em testes críticos e significativos.
Ferramentas: IDEs (Coverage Gutters), CI/CD (Codecov, SonarQube) para visualização, rastreamento e aplicação.
Cobertura e testes robustos são cruciais para a qualidade do software.
Conclusão: O Caminho para o Código Robusto e Confiável
Testes unitários eficazes exigem diretrizes para legibilidade e manutenção.
Princípios F.I.R.S.T. para Testes de Qualidade
- Fast (Rápidos): Executam rapidamente para feedback rápido.
- Isolated (Isolados): Independentes, ordem não importa.
- Repeatable (Repetíveis): Resultados consistentes, sempre.
- Self-Validating (Auto-validáveis): Resultado claro: passou ou falhou.
- Timely (Oportunos): Escritos antes/com o código (TDD).
Estruturando Testes com Arrange-Act-Assert (AAA)
- Arrange: Prepara o ambiente.
- Act: Executa o código.
- Assert: Verifica o resultado.
Nomenclatura Descritiva: describe (unidade), test (comportamento específico).
Um Conceito por Teste: Focar em um único conceito.
Evitar Lógica Complexa: Testes simples; use auxiliares/hooks.
Testes Independentes: Garanta estado limpo (beforeEach/afterEach).
Refatorar Testes: Mantenha testes limpos e eficientes como código.
.only e .skip: Úteis em desenvolvimento, mas nunca commit.
Configuração Avançada do Jest (jest.config.js): Personalize o Jest.
moduleNameMapper: Mapeamento de módulos.setupFilesAfterEnv: Config global.collectCoverage: Relatórios cobertura.testEnvironment: Ambiente.testPathIgnorePatterns: Ignorar paths.
Essas práticas garantem testes de alta qualidade, documentação e resiliência do software.