📚 Módulo 09: DevOps - Testes com Jest, Docker e CI/CD Duplo no GitHub Actions

Neste módulo final do nosso curso TecLoja 03, estudaremos o ciclo de vida completo de entrega contínua e qualidade de software. Desenvolveremos Testes Unitários Assíncronos para a camada de serviços do NestJS utilizando Jest, projetaremos Dockerfiles Multi-stage otimizados para produção, resolveremos o desafio de rotas virtuais SPA no Nginx e automatizaremos o deploy contínuo (CD) na Render e Netlify através de fluxos de trabalho do GitHub Actions.


🚀 Ciclo de CI/CD (Integração e Entrega Contínuas)

Ao utilizar o GitHub Actions, nossa pipeline protege a branch main de código falho rodando o Jest e, se tudo estiver verde, aciona as nuvens.

flowchart LR
    A[Dev: git push origin main] --> B{GitHub Actions}
    B -->|Step 1| C[npm run test]
    C -- Falha --> D[Bloqueia Deploy]
    C -- Sucesso --> E[Chama Webhook Render API]
    C -- Sucesso --> F[Faz Build e Envia pra Netlify]

🧪 1. Testes Unitários com Jest no NestJS

Os testes de software garantem que novas implementações não quebrem regras de negócios existentes. No NestJS, o framework já integra nativamente o executor de testes Jest.

Escreveremos um teste assíncrono para validar a injeção do nosso catálogo. Crie o arquivo de teste em src/produto/produto.service.spec.ts:

// src/produto/produto.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { ProdutoService } from './produto.service';
import { PrismaService } from '../prisma/prisma.service';

const mockPrisma = {
  produto: {
    findMany: jest.fn().mockResolvedValue([
      { id: 1, nome: 'iPhone 15', preco: 8999, estoque: 10, categoriaId: 1 },
    ]),
    findUnique: jest.fn().mockResolvedValue(
      { id: 1, nome: 'iPhone 15', preco: 8999, estoque: 10, categoriaId: 1 }
    ),
  },
};

describe('ProdutoService', () => {
  let service: ProdutoService;
  let prisma: PrismaService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ProdutoService,
        {
          provide: PrismaService,
          useValue: mockPrisma, // Substitui a conexão real pelo mock controlado
        },
      ],
    }).compile();

    service = module.get<ProdutoService>(ProdutoService);
    prisma = module.get<PrismaService>(PrismaService);
  });

  it('deve retornar uma lista completa de produtos do catálogo', async () => {
    const produtos = await service.buscarTodos();
    expect(produtos).toBeDefined();
    expect(produtos.length).toBeGreaterThan(0);
    expect(produtos[0].nome).toBe('iPhone 15');
    expect(prisma.produto.findMany).toHaveBeenCalled();
  });
});

Execute o teste em seu terminal:

npm run test

🐳 2. Conteinerização de Produção com Docker Multi-Stage

O empacotamento em múltiplos estágios (Multi-stage Build) permite isolar as dependências e ferramentas de compilação (SDKs pesados) no primeiro estágio e transferir apenas o código compilado e as dependências mínimas para o estágio final, resultando em imagens seguras e ultra-leves.

A. Dockerfile da API (NestJS Backend)

Crie o arquivo Dockerfile na raiz do seu projeto de backend:

# Stage 1: Build da aplicação NestJS
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
COPY prisma ./prisma/
RUN npm install
COPY . .
RUN npx prisma generate
RUN npm run build

# Stage 2: Imagem final leve de produção
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --only=production
COPY prisma ./prisma/
COPY --from=builder /app/dist ./dist
RUN npx prisma generate

EXPOSE 3000
CMD ["node", "dist/src/main.js"]

B. Dockerfile do Frontend (React SPA) + Fallback Nginx

Em SPAs do React, as rotas (ex: /carrinho ou /admin) são “virtuais” e geridas localmente no navegador pelo React Router. Se um usuário acessar a SPA diretamente pelo domínio principal e clicar nos menus, tudo funciona. Porém, se ele atualizar a página (Refresh/F5) enquanto estiver na tela /carrinho, o servidor Nginx procurará uma pasta física /carrinho/index.html no disco, retornando o indesejado erro 404 Not Found.

Para solucionar isso, usaremos um arquivo customizado de configuração do Nginx (nginx.conf) que redireciona qualquer requisição interna de volta para o index de inicialização, preservando as rotas virtuais.

Crie o arquivo nginx.conf na raiz do seu frontend React:

# nginx.conf
server {
    listen 80;
    server_name localhost;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        # Tenta servir o arquivo solicitado. Se não existir, serve o index.html (SPA Fallback)
        try_files $uri $uri/ /index.html;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

Agora, crie o arquivo Dockerfile de múltiplos estágios na raiz do seu frontend React:

# Stage 1: Compilação estática dos arquivos
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Hospedagem estática com Nginx de alto desempenho
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

🚀 3. Automação de Deploys com GitHub Actions CI/CD

Para garantir a integração contínua (CI) e entrega contínua (CD), criaremos workflows automatizados que rodam a cada commit efetuado na branch main.

A. CI/CD do Backend NestJS (Deploy na Render)

Crie a pasta .github/workflows/ no repositório de backend e adicione o arquivo deploy.yml:

# .github/workflows/deploy.yml
name: CI/CD Pipeline - NestJS Backend

on:
  push:
    branches: [ main ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: 18
        cache: 'npm'
    - name: Install dependencies
      run: npm install
    - name: Run Unit Tests
      run: npm run test

  deploy-to-render:
    needs: build-and-test
    runs-on: ubuntu-latest
    steps:
    - name: Trigger Render Webhook Deploy
      run: |
        curl -X POST https://api.render.com/deploy/srv-$?key=$

B. CI/CD do Frontend React (Deploy na Netlify)

Crie a pasta .github/workflows/ no repositório de frontend e adicione o arquivo deploy.yml:

# .github/workflows/deploy.yml
name: CI/CD Pipeline - React Frontend

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: 18
        cache: 'npm'
    - name: Install dependencies
      run: npm install
    - name: Compile Production Bundle
      run: npm run build
    - name: Deploy static files to Netlify CDN
      uses: nwtgck/actions-netlify@v2.0
      with:
        publish-dir: './dist'
        production-deploy: true
        github-token: $
        deploy-message: "Deploy automatizado do GitHub Actions"
      env:
        NETLIFY_AUTH_TOKEN: $
        NETLIFY_SITE_ID: $


✅ Pré-Requisitos deste Módulo

Antes de realizar a validação final e auto-avaliação do projeto, certifique-se de que:


🤔 Por que fizemos assim?


🔍 Checkpoint

  1. Sucesso no Jest: Execute o comando npm run test no backend NestJS e confirme se todos os testes unitários foram validados sem erros.
  2. Tamanho de Imagem Enxuto: Compile a imagem do Docker localmente e certifique-se de que os estágios temporários foram descartados de forma automática.
  3. F5 sem Falhas no Nginx: Monte o container Docker do React com o Nginx e simule atualizações manuais no navegador em páginas virtuais. O conteúdo deve se manter operacional sem exibir erros de página do Nginx.

⚠️ Erros Comuns

Erro Causa Solução
Os testes unitários travam ou acusam conexões abertas pendentes no banco de dados Ausência de mocks para conexões diretas do PrismaService na suíte de testes do Jest. Certifique-se de injetar a classe PrismaService apontando para o objeto mockado (mockPrisma) com todas as funções de consulta devidamente simuladas.
O comando npx prisma generate falha no builder do Dockerfile NestJS Falta de cópia do arquivo schema.prisma antes da execução do gerador. Certifique-se de incluir a linha COPY prisma ./prisma/ antes de disparar o comando de compilação do Prisma Client no arquivo Dockerfile.
Deploy travado no GitHub Actions alegando erros de segredos ausentes Os tokens criptográficos do Netlify ou Render não foram informados no painel de segredos do GitHub. Navegue até as configurações do repositório no GitHub (Settings -> Secrets and variables -> Actions) e adicione os segredos com os nomes correspondentes aos workflows.

📝 4. Checklist de Auto-Avaliação (Rubrica do Estudante)

Para apoiar a validação diagnóstica dos estudantes, disponibilize esta rubrica de auto-verificação prática:

Requisito Ação Esperada [x]
Banco de Dados O arquivo schema.prisma mapeia as relações 1:N e N:M (com dados históricos)? [ ]
Migrations As alterações físicas foram versionadas e aplicadas via prisma migrate? [ ]
Seeding O script seed.ts popula categorias, produtos e senhas com bcrypt? [ ]
Erros da API O filtro global HttpExceptionFilter formata os erros do banco em JSON uniforme? [ ]
Transações ACID A compra deduz estoque de forma atômica e executa rollback automático se faltar itens? [ ]
Segurança As rotas administrativas exigem token JWT ativo e privilégio ADMIN validado por Guards? [ ]
Reatividade O gancho useCart recalcula o total de itens e preços dinamicamente no React? [ ]
Qualidade Os testes de serviço passam com sucesso rodando npm run test com mocks de banco? [ ]
Docker Os Dockerfiles utilizam divisão de múltiplos estágios para otimizar tamanhos? [ ]
Nginx SPA Fallback Atualizar a página na rota /carrinho preserva a tela (sem erro 404)? [ ]

🏁 Encerramento do Curso

Parabéns! Você desenvolveu um sistema distribuído multirepo completo, resiliente, criptografado e automatizado no ecossistema JavaScript moderno.

Os conceitos aprendidos aqui sobre Engenharia de Software (Boas Práticas, Padrões SOLID, Injeção de Dependências, Qualidade de Código e Integração Contínua) e Banco de Dados (Modelagem física, Relacionamentos Complexos e Transações ACID robustas) servirão como alicerce fundamental para a sua carreira profissional de desenvolvedor!


Voltar para o Sumário