Guía de Pruebas
Tabla de Contenidos
- Propósito
- ¿Para quién es esto?
- Estrategia de Pruebas y Pirámide
- Pruebas de Microservicios NestJS (Jest)
- Pruebas de Dashboard React (Vitest y Playwright)
- Pruebas de API Gateway
- Cobertura de Pruebas y Estándares de Calidad
- Integración CI/CD
- Mejores Prácticas
- Solución de Problemas de Pruebas
Propósito
Esta guía proporciona instrucciones completas para ejecutar, escribir y mantener pruebas en la plataforma Algesta. Cubre pruebas unitarias, pruebas de integración y pruebas de extremo a extremo (E2E) para microservicios NestJS y el dashboard React, asegurando calidad de código y previniendo regresiones.
Siguiendo esta guía, podrás:
- Entender la pirámide y estrategia de pruebas
- Ejecutar pruebas localmente y en CI/CD
- Escribir pruebas unitarias, de integración y E2E efectivas
- Lograr y mantener más del 90% de cobertura de código
- Depurar pruebas fallidas eficientemente
¿Para quién es esto?
Esta guía es principalmente para desarrolladores de software escribiendo y ejecutando pruebas, e ingenieros QA validando funcionalidad. Asume familiaridad con Jest, Vitest, Playwright y mejores prácticas de pruebas.
Pruebas Strategy and Pyramid
The Algesta platform follows the Pruebas Pyramid strategy, emphasizing fast, isolated unit Pruebas at the base and fewer, more comprehensive E2E Pruebas at the top.
graph TD
subgraph Testing Pyramid
E2E[E2E Tests<br/>Few: Full user flows<br/>Playwright, Supertest]
Integration[Integration Tests<br/>Some: API + DB interactions<br/>Jest with test DB]
Unit[Unit Tests<br/>Many: Functions, services<br/>Jest, Vitest]
end
Unit --> Integration
Integration --> E2E
style E2E fill:#ff6b6b,stroke:#c92a2a,color:#fff
style Integration fill:#ffa94d,stroke:#e67700,color:#fff
style Unit fill:#51cf66,stroke:#2b8a3e,color:#fff
Test Types
| Test Type | Purpose | Coverage Target | Execution Speed | Tools |
|---|---|---|---|---|
| Unit | Test individual functions, Métodos, Componentes in isolation | 80%+ | Fast (~ms) | Jest, Vitest |
| Integration | Test interactions between modules, Base de datos, external services | 60%+ | Medium (~100ms) | Jest with test DB |
| E2E | Test Completo user workflows through UI or API | Critical paths | Slow (~seconds) | Playwright, Supertest |
Coverage Targets
| Service | Unit | Integration | E2E | Total Target |
|---|---|---|---|---|
| Orders MS | 85% | 70% | Critical paths | >90% |
| Notifications MS | 85% | 65% | Email delivery | >90% |
| Provider MS | 85% | 70% | Auction flows | >90% |
| API Gateway | 80% | 75% | Routing, auth | >90% |
| Dashboard | 75% | N/A | Key user flows | >75% |
NestJS Microservicios Pruebas (Jest)
All NestJS Microservicios (Orders, Notifications, Provider, Gateway) use Jest for unit and integration Pruebas.
Test File Structure
algesta-ms-orders-nestjs/├── src/│ ├── domain/│ │ ├── entities/│ │ │ └── order.entity.ts│ │ ├── services/│ │ │ ├── order.service.ts│ │ │ └── order.service.spec.ts <- Unit tests│ ├── application/│ │ ├── commands/│ │ │ ├── create-order.command.ts│ │ │ ├── create-order.handler.ts│ │ │ └── create-order.handler.spec.ts│ ├── infrastructure/│ │ ├── controllers/│ │ │ ├── order.controller.ts│ │ │ └── order.controller.spec.ts <- Integration tests├── test/│ ├── jest-e2e.json <- E2E config│ └── app.e2e-spec.ts <- E2E testsConvention: Test files are co-located with source files, ending in .spec.ts.
Running Pruebas
Unit Pruebas
cd algesta-ms-orders-nestjs
# Run all testsnpm run test
# Run tests in watch mode (auto-rerun on file changes)npm run test:watch
# Run tests with coverage reportnpm run test:cov
# Run specific test filenpm run test -- order.service.spec.ts
# Run tests matching patternnpm run test -- --testNamePattern="should create order"Expected Output:
PASS src/domain/services/order.service.spec.ts OrderService ✓ should create order with valid data (15 ms) ✓ should throw error for invalid data (5 ms)
Test Suites: 1 passed, 1 totalTests: 2 passed, 2 totalCoverage: 92.5% Statements 185/200 90.0% Branches 27/30 95.0% Functions 38/40 92.5% Lines 180/195Verification: Coverage should be >90%. If lower, see Test Coverage.
Integration Pruebas
Integration Pruebas hit real Base de datos (test instance) and external services (mocked or sandboxed).
# Run E2E/integration testsnpm run test:e2e
# With coveragenpm run test:covSetup: Integration Pruebas require test Base de datos. Set MONGODB_URI to test instance:
MONGODB_URI=mongodb://localhost:27017/orders_ms_testVerification:
# After tests, check test databasemongosh mongodb://localhost:27017/orders_ms_test --eval "db.orders.countDocuments()"# Expected: Documents created/cleaned up by testsDebug Pruebas
# Run in debug mode (attach debugger)npm run test:debugThen attach debugger (VS Code):
- Add breakpoint in test file
- Run “Debug: Attach to Node Process”
- Select the Jest process
Test Structure
Example Unit Test: order.service.spec.ts
import { Test, TestingModule } from "@nestjs/testing";import { OrderService } from "./order.service";import { getModelToken } from "@nestjs/mongoose";import { Order } from "../entities/order.entity";import { mock, mockDeep } from "jest-mock-extended";
describe("OrderService", () => { let service: OrderService; let orderModel: any;
beforeEach(async () => { orderModel = mockDeep<Model<Order>>();
const module: TestingModule = await Test.createTestingModule({ providers: [ OrderService, { provide: getModelToken(Order.name), useValue: orderModel, }, ], }).compile();
service = module.get<OrderService>(OrderService); });
it("should be defined", () => { expect(service).toBeDefined(); });
describe("createOrder", () => { it("should create order with valid data", async () => { const createDto = { service: "Air Conditioning Repair", address: "Calle 123 #45-67", city: "Bogotá", };
const savedOrder = { ...createDto, orderId: "ORDER-12345" }; orderModel.create.mockResolvedValue(savedOrder);
const result = await service.createOrder(createDto);
expect(result).toEqual(savedOrder); expect(orderModel.create).toHaveBeenCalledWith(createDto); });
it("should throw error for missing required fields", async () => { const invalidDto = { service: "Test" }; // Missing address, city
await expect(service.createOrder(invalidDto)).rejects.toThrow(); }); });});Key Patterns:
- Mocking: Use
jest-mock-extendedfor type-safe mocks - Test Isolation: Each test is independent (no shared state)
- AAA Pattern: Arrange (setup), Act (execute), Assert (verify)
- Edge Cases: Test happy path, error cases, boundary conditions
React Dashboard Pruebas (Vitest & Playwright)
The React dashboard (algesta-dashboard-react) uses Vitest for unit/Componente Pruebas and Playwright for E2E Pruebas.
Test File Structure
algesta-dashboard-react/├── src/│ ├── components/│ │ ├── Button/│ │ │ ├── Button.tsx│ │ │ └── Button.test.tsx <- Component tests│ ├── features/│ │ ├── orders/│ │ │ ├── api/│ │ │ │ ├── getOrders.ts│ │ │ │ └── getOrders.test.ts <- API hook tests│ ├── testing/│ │ ├── e2e/│ │ │ ├── login.spec.ts <- Playwright E2E tests│ │ │ └── orders.spec.ts│ │ ├── mocks/│ │ │ └── handlers.ts <- MSW mock handlersRunning Vitest Pruebas
cd algesta-dashboard-react
# Run all tests (watch mode)pnpm test
# Run once (CI mode)pnpm test:ci
# With UI (interactive test viewer)pnpm test:ui
# With coveragepnpm coverageExpected Output:
✓ src/components/Button/Button.test.tsx (2)✓ src/features/orders/api/getOrders.test.ts (3)
Test Files 2 passed (2) Tests 5 passed (5) Duration 1.23s
Coverage: 78.5% Statements (785/1000)Verification: Coverage should be >75% for UI Componentes.
Running Playwright E2E Pruebas
# Run all E2E tests headlesspnpm test:e2e
# Run in specific browserpnpm test:e2e:chrome
# Run with UI (interactive mode)pnpm test:e2e:ui
# Debug mode (step through tests)pnpm test:e2e:debug
# Generate tests interactivelypnpm test:e2e:codegen http://localhost:5173Expected Output:
Running 10 tests using 2 workers
✓ [chromium] › login.spec.ts:3:1 › should login with valid credentials (2.1s) ✓ [chromium] › orders.spec.ts:5:1 › should display orders list (1.8s)
10 passed (15.3s)Playwright Configuration
File: algesta-dashboard-react/playwright.config.ts
export default defineConfig({ testDir: "./src/testing/e2e", fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, // Retry failed tests in CI workers: process.env.CI ? 1 : undefined, // Parallel in local, sequential in CI reporter: "html", // HTML report
use: { trace: "on-first-retry", // Capture trace on failures },
projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] } }, { name: "webkit", use: { ...devices["Desktop Safari"] } }, ],});Key Settings:
- testDir: E2E test location
- retries: Auto-retry flaky Pruebas in CI
- trace: Capture screenshots/videos on failures
- projects: Run Pruebas in multiple browsers
Example E2E Test
File: src/Pruebas/e2e/orders.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Orders Management", () => { test.beforeEach(async ({ page }) => { // Login before each test await page.goto("http://localhost:5173/login"); await page.fill('input[name="email"]', "admin@algesta.com"); await page.fill('input[name="password"]', "password123"); await page.click('button[type="submit"]'); await expect(page).toHaveURL(/.*dashboard/); });
test("should display orders list", async ({ page }) => { await page.goto("http://localhost:5173/orders");
// Wait for orders to load await page.waitForSelector('[data-testid="orders-table"]');
// Verify table has rows const rows = await page.locator("tbody tr").count(); expect(rows).toBeGreaterThan(0); });
test("should create new order", async ({ page }) => { await page.goto("http://localhost:5173/orders/new");
await page.fill('input[name="service"]', "Air Conditioning Repair"); await page.fill('input[name="address"]', "Calle 123 #45-67"); await page.fill('input[name="city"]', "Bogotá"); await page.click('button[type="submit"]');
// Verify success message await expect(page.locator(".toast-success")).toBeVisible(); await expect(page).toHaveURL(/.*orders\/ORDER-\d+/); });});Best Practices:
- Selectors: Use
data-testidfor stable selectors - Wait Strategies: Use
waitForSelector,waitForLoadStateinstead ofsetTimeout - Isolation: Each test independent, cleanup after Pruebas
- Assertions: Use Playwright’s auto-retry assertions
API Gateway Pruebas
The API Gateway has unique Pruebas needs: routing, rate limiting, authentication.
Running Pruebas
cd algesta-api-gateway-nestjs
# Unit testsnpm run test
# Integration tests (E2E)npm run test:e2e
# Coveragenpm run test:covIntegration Test Example
File: test/app.e2e-spec.ts
import { Test, TestingModule } from "@nestjs/testing";import { INestApplication } from "@nestjs/common";import * as request from "supertest";import { AppModule } from "../src/app.module";
describe("API Gateway (e2e)", () => { let app: INestApplication; let jwtToken: string;
beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();
app = moduleFixture.createNestApplication(); await app.init();
// Obtain JWT token for authenticated requests const loginResponse = await request(app.getHttpServer()) .post("/auth/login") .send({ email: "test@algesta.com", password: "password123" });
jwtToken = loginResponse.body.access_token; });
afterAll(async () => { await app.close(); });
describe("/health (GET)", () => { it("should return health status", () => { return request(app.getHttpServer()) .get("/health") .expect(200) .expect((res) => { expect(res.body.status).toBe("ok"); }); }); });
describe("/orders (POST)", () => { it("should route to orders microservice and create order", () => { return request(app.getHttpServer()) .post("/orders") .set("Authorization", `Bearer ${jwtToken}`) .send({ service: "Test Service", address: "Test Address", city: "Bogotá", }) .expect(201) .expect((res) => { expect(res.body.orderId).toBeDefined(); }); });
it("should reject unauthorized requests", () => { return request(app.getHttpServer()) .post("/orders") .send({ service: "Test" }) .expect(401); }); });
describe("Rate Limiting", () => { it("should block requests exceeding rate limit", async () => { const requests = Array.from({ length: 105 }, () => request(app.getHttpServer()).get("/health") );
const responses = await Promise.all(requests); const blockedRequests = responses.filter((r) => r.status === 429);
expect(blockedRequests.length).toBeGreaterThan(0); }); });});Test Coverage and Quality Standards
Measuring Coverage
NestJS (Jest):
npm run test:covReact (Vitest):
pnpm coverageCoverage Report Location:
- NestJS:
coverage/lcov-report/index.html - React:
coverage/index.html
Open in browser to see detailed coverage by file.
Coverage Métricas
| Métrica | Description | Target |
|---|---|---|
| Statements | % of statements executed | >90% |
| Branches | % of conditional branches covered | >85% |
| Functions | % of functions called | >90% |
| Lines | % of code lines executed | >90% |
Improving Coverage
Identify Uncovered Code:
# Generate coverage reportnpm run test:cov
# Open reportopen coverage/lcov-report/index.html
# Find red/yellow highlighted lines (uncovered)Write Missing Pruebas:
- Identify uncovered functions/branches
- Add test cases for each branch
- Verify coverage increased
Example:
// Before: 50% coverage (only happy path tested)it("should create order", async () => { const result = await service.createOrder(validDto); expect(result).toBeDefined();});
// After: 100% coverage (error path added)it("should throw error for invalid data", async () => { await expect(service.createOrder(invalidDto)).rejects.toThrow();});CI/CD Integration
Pruebas run automatically in Azure Pipelines on every commit and tag-based Despliegue.
Azure Pipeline Test Stage
File: algesta-ms-orders-nestjs/azure-pipelines.yml
- stage: Build displayName: "Test and Analyze" jobs: - job: Build steps: - task: NodeTool@0 inputs: versionSpec: "21.x" displayName: "Install Node.js"
- script: | npm install --force npm run build npm run test:cov continueOnError: true displayName: "npm install, build, and test"
- task: PublishCodeCoverageResults@2 inputs: summaryFileLocation: "$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml"Key Points:
- Pruebas run on Node 21.x
npm run test:covgenerates coverage report- Coverage published to Azure DevOps (viewable in pipeline)
continueOnError: trueallows build to proceed even if Pruebas fail (⚠️ consider removing for stricter CI)
Running Pruebas Locally Before Push
# Ensure tests passnpm run test:cov
# Check coverage meets target (>90%)grep -A 5 "Coverage summary" coverage/coverage-summary.txt
# Lint codenpm run lint
# Buildnpm run buildGit Hook (Husky): Some repos have pre-commit hooks that run Pruebas automatically:
# Configured in .husky/pre-commitnpm run testnpm run lintBest Practices
1. Test Isolation
Bad:
let sharedOrder; // Shared state across tests
it("should create order", async () => { sharedOrder = await service.createOrder(dto);});
it("should update order", async () => { await service.updateOrder(sharedOrder.id, updates); // Depends on previous test});Good:
it("should update order", async () => { const order = await service.createOrder(dto); // Create within test const result = await service.updateOrder(order.id, updates); expect(result).toBeDefined();});2. Mock External Dependencies
Mock Base de datos:
const orderModel = mockDeep<Model<Order>>();orderModel.create.mockResolvedValue(savedOrder);Mock HTTP Requests (MSW for React):
import { rest } from "msw";import { setupServer } from "msw/node";
const server = setupServer( rest.get("/api/orders", (req, res, ctx) => { return res(ctx.json([{ orderId: "ORDER-1" }])); }));
beforeAll(() => server.listen());afterAll(() => server.close());3. Test Edge Cases
Checklist:
- ✓ Happy path (valid input)
- ✓ Invalid input (validation errors)
- ✓ Boundary conditions (empty arrays, max Valors)
- ✓ Error handling (network failures, Base de datos errors)
- ✓ Edge cases (null, undefined, special characters)
4. Use Descriptive Test Names
Bad:
it("should work", () => {});Good:
it("should create order with valid data and return orderId", () => {});it("should throw ValidationError when address is missing", () => {});5. Avoid Pruebas Implementación Details
Bad (Pruebas internal state):
it("should call private method _validateOrder", () => { const spy = jest.spyOn(service as any, "_validateOrder"); service.createOrder(dto); expect(spy).toHaveBeenCalled();});Good (Pruebas public behavior):
it("should throw error for invalid order", async () => { await expect(service.createOrder(invalidDto)).rejects.toThrow( "Invalid address" );});Troubleshooting Pruebas
Issue: Pruebas pass locally but fail in CI
Diagnosis:
- Environment differences (Node version, dependencies)
- Timezone issues
- Race conditions (parallel Pruebas)
Solutions:
- Verify Node version matches CI:
Ventana de terminal node --version# Should match azure-pipelines.yml versionSpec - Run Pruebas with
--runInBand(sequential):Ventana de terminal npm run test -- --runInBand - Use deterministic test data (avoid
Date.now(), use mocked time)
Issue: Flaky E2E Pruebas (intermittent failures)
Common Causes:
- Network delays
- Animation timing
- Async state updates
Solutions:
-
Use Playwright’s auto-retry assertions:
// Bad: May fail if element not yet visibleexpect(await page.locator(".button").isVisible()).toBe(true);// Good: Retries until visible or timeoutawait expect(page.locator(".button")).toBeVisible(); -
Increase timeout for slow Operaciones:
await page.waitForSelector(".orders-table", { timeout: 10000 }); -
Disable animations in test environment:
* {animation-duration: 0s !important;}
Issue: Coverage lower than expected
Diagnosis:
npm run test:covopen coverage/lcov-report/index.htmlSolutions:
- Identify uncovered branches (highlighted in red/yellow)
- Add Pruebas for error paths, edge cases
- Remove dead code (if 0% coverage)
Related Guías:
- Local Development Setup: Running services for integration Pruebas
- CI/CD Guía: Test automation in pipelines
- Troubleshooting: Debugging test failures
For Support:
- Review test examples in each repo’s
test/directory - Check Jest/Vitest/Playwright Documentoation
- Run Pruebas with
--verbosefor detailed output