📚 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:
- O fluxo de checkout e CRUD administrativo foram testados no frontend React (Módulo 08).
- Você possui as credenciais de deploys (Render/Netlify) e chaves do GitHub prontas para integração.
- Possui o executável do Docker configurado localmente (caso pretenda validar a compilação local de containers).
🤔 Por que fizemos assim?
- Por que usar
TestingModulee simular retornos com mocks controlados (mockPrisma) no Jest? Rodar testes unitários integrados a bancos de dados físicos de testes torna a pipeline de build lenta, propensa a falhas de conexão de rede externa e dependente de scripts pesados de limpeza física. Usar mocks com oTestingModulesubstitui a dependência real pelo objeto falso e predefinido, isolando o teste unicamente nas regras de negócio contidas nos métodos doProdutoServiceem milissegundos na esteira de CI/CD. - Por que separar o empacotamento em estágios (Multi-stage) no Docker do NestJS e React? O estágio de compilação exige ferramentas pesadas e dependências de desenvolvimento (que incham a imagem para até 1GB). Com o Multi-stage, instalamos o compilador e geramos a pasta de build (
dist) em um estágio temporário descartável. No estágio final, copiamos apenas o build finalizado e as dependências mínimas de execução de produção, reduzindo a imagem final para cerca de 150MB no NestJS e pouquíssimos megabytes no frontend servido via Nginx Alpine, economizando infraestrutura e acelerando o deploy. - Por que declarar a instrução
try_filesnas configurações de servidor do Nginx? Em Single Page Applications, as rotas (como/carrinhoou/admin) são puramente virtuais, controladas no cliente via JavaScript (React Router). Se o usuário atualizar a página (F5) enquanto navega, o Nginx não encontrará nenhuma pasta ou arquivo físico correspondente e retornará erro 404 Not Found. A linhatry_files $uri $uri/ /index.htmlinstrui o servidor Nginx a retornar oindex.htmlraiz caso a busca falhe no disco, delegando o processamento da URL para o roteador virtual do React.
🔍 Checkpoint
- Sucesso no Jest: Execute o comando
npm run testno backend NestJS e confirme se todos os testes unitários foram validados sem erros. - 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.
- 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!