GitHub Actions: testes automatizados em Node.js

Testes automáticos no CI bloqueiam merges com código quebrado, sem precisar de ninguém revisar manualmente se "ainda funciona". Com GitHub Actions, cada PR exibe cobertura de código, relatório de falhas e pode bloquear o merge até todos os testes passarem.

Workflow de testes com Vitest

Pipeline de testes para projetos TypeScript com Vitest:

yaml
# .github/workflows/tests.yml
name: Testes

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [20, 22]   # testar em múltiplas versões

    steps:
      - uses: actions/checkout@v4

      - name: Configurar Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Instalar dependências
        run: npm ci

      - name: Verificar TypeScript
        run: npx tsc --noEmit

      - name: Lint
        run: npm run lint

      - name: Testes unitários
        run: npm run test:unit -- --reporter=verbose

      - name: Relatório de cobertura
        run: npm run test:coverage

      - name: Upload cobertura para Codecov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage/lcov.info
          fail_ci_if_error: false

Testes de integração com banco de dados real

Rodar testes de integração com PostgreSQL e Redis no CI:

yaml
# .github/workflows/tests.yml — job de integração:
  integration:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-retries 5
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - run: npm ci

      - name: Rodar migrations no banco de teste
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
        run: npm run db:migrate

      - name: Testes de integração
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
          NODE_ENV: test
          JWT_SECRET: segredo-de-teste-123
        run: npm run test:integration

Configurar Vitest para CI

vitest.config.ts com configurações para ambiente de CI:

typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import path from 'path'

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',

    // Separar testes unitários de integração por pasta:
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
    exclude: ['src/**/*.integration.test.ts'],

    // Configuração de cobertura:
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov', 'html'],
      include: ['src/**/*.ts'],
      exclude: ['src/**/*.test.ts', 'src/types/**'],
      thresholds: {
        lines: 70,        // bloquear CI se cobertura < 70%
        functions: 70,
        branches: 60,
        statements: 70,
      },
    },

    // Timeout mais longo para integração:
    testTimeout: 30_000,

    // Relatório para o GitHub Actions (faz anotações no PR):
    reporter: process.env.CI ? ['verbose', 'github-actions'] : ['verbose'],

    // Rodar em paralelo (mais rápido no CI):
    pool: 'threads',
    poolOptions: {
      threads: { maxThreads: 4 },
    },
  },
  resolve: {
    alias: { '@': path.resolve(__dirname, 'src') },
  },
})

Comentar cobertura de código no Pull Request

Exibir diff de cobertura diretamente nos comentários do PR:

yaml
# .github/workflows/tests.yml — adicionar após os testes:
      - name: Relatório de cobertura no PR
        uses: davelosert/vitest-coverage-report-action@v2
        if: always()
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          # Comentar no PR com o relatório de cobertura:
          # - linhas novas sem cobertura ficam marcadas
          # - comparação com a cobertura da branch main
          json-summary-path: ./coverage/coverage-summary.json
          json-final-path: ./coverage/coverage-final.json

# package.json — scripts de teste:
# {
#   "scripts": {
#     "test": "vitest run",
#     "test:unit": "vitest run --exclude='**/*.integration.*'",
#     "test:integration": "vitest run --include='**/*.integration.*'",
#     "test:coverage": "vitest run --coverage",
#     "test:watch": "vitest watch"
#   }
# }

Bloquear merge se testes falharem

Configurar branch protection para exigir CI verde antes do merge:

bash
# No GitHub — Settings → Branches → Branch protection rules:
# Branch name pattern: main
# Marcar: ✓ Require status checks to pass before merging
# Status checks required:
#   - test (Node.js 22)
#   - integration
#   - TypeScript check
# Marcar: ✓ Require branches to be up to date before merging

# Resultado:
# - PRs com testes vermelhos não podem ser mergeados
# - O botão de merge fica bloqueado
# - Cada push no PR roda os testes automaticamente

# Tornar o workflow obrigatório (required check):
# O nome do check é o campo "name:" do job no workflow
# Exemplo: se o job se chama "test", o check é "test"

# Para múltiplos SO ou versões (matrix):
# Cada combinação gera um check separado:
# "test (ubuntu-latest, 20)"
# "test (ubuntu-latest, 22)"
# Adicione todos que quiser como obrigatórios

Não quer configurar manualmente?

Implante o VPS para APIs em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Vitest é a escolha moderna para projetos TypeScript/ESM, configuração zero para projetos Vite/Vitest, execução em paralelo nativa, compatível com a API do Jest, watch mode muito mais rápido. Jest ainda é mais maduro em ecossistema de plugins (enzyme, etc.) e comum em projetos legados. Para projetos novos em TypeScript: Vitest. Para migrar um projeto Jest existente: migração é simples, a API é compatível.

Três abordagens: (1) Mock: substituir a chamada HTTP por um mock (msw, nock), rápido, sem dependência externa, mas não testa a integração real. (2) VCR/cassete: gravar as respostas reais uma vez e reproduzir nos testes, bom para APIs estáveis. (3) Sandbox: usar ambiente de sandbox do provedor (Stripe test mode, Mercado Pago sandbox), testa a integração real sem custo. Para testes unitários: mocks. Para testes e2e: sandbox.

100% de cobertura é um objetivo falso, você pode ter 100% de cobertura e zero testes úteis. Uma boa heurística: 70-80% de cobertura de linhas para projetos maduros, com foco em cobrir os caminhos críticos (autenticação, pagamentos, regras de negócio). Código de IO (controllers, rotas) é mais difícil de cobrir e menos crítico que a lógica de negócio. Cobertura < 40% geralmente indica falta de testes.

Cache de node_modules (actions/cache): elimina o npm install em cada run. Paralelismo: vitest com pool: threads roda suites em paralelo. Split de testes: separar unitários (rápidos, sem serviços) de integração (lentos, com banco) em jobs diferentes, os unitários rodam em < 30s, integração em paralelo. Para testes muito lentos: investigate o motivo, geralmente é banco sem índice ou chamada HTTP real vazando.

Codecov Free: gratuito para repositórios públicos. Para privados: 5 usuários grátis no plano Team, depois $10/usuário/mês. Alternativas gratuitas self-hosted: Coveralls (plano gratuito para privados), SonarQube Community (self-hosted), ou simplesmente publicar o relatório HTML como artefato do GitHub Actions sem serviço externo (actions/upload-artifact + pages).