Saltearse al contenido

Guía de Pruebas

Tabla de Contenidos

  1. Propósito
  2. ¿Para quién es esto?
  3. Estrategia de Pruebas y Pirámide
  4. Pruebas de Microservicios NestJS (Jest)
  5. Pruebas de Dashboard React (Vitest y Playwright)
  6. Pruebas de API Gateway
  7. Cobertura de Pruebas y Estándares de Calidad
  8. Integración CI/CD
  9. Mejores Prácticas
  10. 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 TypePurposeCoverage TargetExecution SpeedTools
UnitTest individual functions, Métodos, Componentes in isolation80%+Fast (~ms)Jest, Vitest
IntegrationTest interactions between modules, Base de datos, external services60%+Medium (~100ms)Jest with test DB
E2ETest Completo user workflows through UI or APICritical pathsSlow (~seconds)Playwright, Supertest

Coverage Targets

ServiceUnitIntegrationE2ETotal Target
Orders MS85%70%Critical paths>90%
Notifications MS85%65%Email delivery>90%
Provider MS85%70%Auction flows>90%
API Gateway80%75%Routing, auth>90%
Dashboard75%N/AKey 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 tests

Convention: Test files are co-located with source files, ending in .spec.ts.

Running Pruebas

Unit Pruebas

Ventana de terminal
cd algesta-ms-orders-nestjs
# Run all tests
npm run test
# Run tests in watch mode (auto-rerun on file changes)
npm run test:watch
# Run tests with coverage report
npm run test:cov
# Run specific test file
npm run test -- order.service.spec.ts
# Run tests matching pattern
npm 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 total
Tests: 2 passed, 2 total
Coverage: 92.5% Statements 185/200
90.0% Branches 27/30
95.0% Functions 38/40
92.5% Lines 180/195

Verification: 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).

Ventana de terminal
# Run E2E/integration tests
npm run test:e2e
# With coverage
npm run test:cov

Setup: Integration Pruebas require test Base de datos. Set MONGODB_URI to test instance:

MONGODB_URI=mongodb://localhost:27017/orders_ms_test

Verification:

Ventana de terminal
# After tests, check test database
mongosh mongodb://localhost:27017/orders_ms_test --eval "db.orders.countDocuments()"
# Expected: Documents created/cleaned up by tests

Debug Pruebas

Ventana de terminal
# Run in debug mode (attach debugger)
npm run test:debug

Then attach debugger (VS Code):

  1. Add breakpoint in test file
  2. Run “Debug: Attach to Node Process”
  3. 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-extended for 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 handlers

Running Vitest Pruebas

Ventana de terminal
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 coverage
pnpm coverage

Expected 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

Ventana de terminal
# Run all E2E tests headless
pnpm test:e2e
# Run in specific browser
pnpm test:e2e:chrome
# Run with UI (interactive mode)
pnpm test:e2e:ui
# Debug mode (step through tests)
pnpm test:e2e:debug
# Generate tests interactively
pnpm test:e2e:codegen http://localhost:5173

Expected 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-testid for stable selectors
  • Wait Strategies: Use waitForSelector, waitForLoadState instead of setTimeout
  • 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

Ventana de terminal
cd algesta-api-gateway-nestjs
# Unit tests
npm run test
# Integration tests (E2E)
npm run test:e2e
# Coverage
npm run test:cov

Integration 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):

Ventana de terminal
npm run test:cov

React (Vitest):

Ventana de terminal
pnpm coverage

Coverage 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étricaDescriptionTarget
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:

Ventana de terminal
# Generate coverage report
npm run test:cov
# Open report
open coverage/lcov-report/index.html
# Find red/yellow highlighted lines (uncovered)

Write Missing Pruebas:

  1. Identify uncovered functions/branches
  2. Add test cases for each branch
  3. 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:cov generates coverage report
  • Coverage published to Azure DevOps (viewable in pipeline)
  • continueOnError: true allows build to proceed even if Pruebas fail (⚠️ consider removing for stricter CI)

Running Pruebas Locally Before Push

Ventana de terminal
# Ensure tests pass
npm run test:cov
# Check coverage meets target (>90%)
grep -A 5 "Coverage summary" coverage/coverage-summary.txt
# Lint code
npm run lint
# Build
npm run build

Git Hook (Husky): Some repos have pre-commit hooks that run Pruebas automatically:

Ventana de terminal
# Configured in .husky/pre-commit
npm run test
npm run lint

Best 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:

  1. Verify Node version matches CI:
    Ventana de terminal
    node --version
    # Should match azure-pipelines.yml versionSpec
  2. Run Pruebas with --runInBand (sequential):
    Ventana de terminal
    npm run test -- --runInBand
  3. 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:

  1. Use Playwright’s auto-retry assertions:

    // Bad: May fail if element not yet visible
    expect(await page.locator(".button").isVisible()).toBe(true);
    // Good: Retries until visible or timeout
    await expect(page.locator(".button")).toBeVisible();
  2. Increase timeout for slow Operaciones:

    await page.waitForSelector(".orders-table", { timeout: 10000 });
  3. Disable animations in test environment:

    * {
    animation-duration: 0s !important;
    }

Issue: Coverage lower than expected

Diagnosis:

Ventana de terminal
npm run test:cov
open coverage/lcov-report/index.html

Solutions:

  1. Identify uncovered branches (highlighted in red/yellow)
  2. Add Pruebas for error paths, edge cases
  3. Remove dead code (if 0% coverage)

Related Guías:

For Support:

  • Review test examples in each repo’s test/ directory
  • Check Jest/Vitest/Playwright Documentoation
  • Run Pruebas with --verbose for detailed output