# Backend Testing Strategies

Comprehensive testing approaches, frameworks, and quality assurance practices (2025).

## Test Pyramid (70-20-10 Rule)

```
        /\
       /E2E\     10% - End-to-End Tests
      /------\
     /Integr.\ 20% - Integration Tests
    /----------\
   /   Unit     \ 70% - Unit Tests
  /--------------\
```

**Rationale:**

- Unit tests: Fast, cheap, isolate bugs quickly
- Integration tests: Verify component interactions
- E2E tests: Expensive, slow, but validate real user flows

## Unit Testing

### Frameworks by Language

**TypeScript/JavaScript:**

- **Vitest** - 50% faster than Jest in CI/CD, ESM native
- **Jest** - Mature, large ecosystem, snapshot testing

**Python:**

- **Pytest** - Industry standard, fixtures, parametrization
- **Unittest** - Built-in, standard library

**Go:**

- **testing** - Built-in, table-driven tests
- **testify** - Assertions and mocking

### Best Practices

```typescript
// Good: Test single responsibility
describe("UserService", () => {
  describe("createUser", () => {
    it("should create user with valid data", async () => {
      const userData = { email: "test@example.com", name: "Test" };
      const user = await userService.createUser(userData);

      expect(user).toMatchObject(userData);
      expect(user.id).toBeDefined();
    });

    it("should throw error with duplicate email", async () => {
      const userData = { email: "existing@example.com", name: "Test" };

      await expect(userService.createUser(userData)).rejects.toThrow(
        "Email already exists",
      );
    });

    it("should hash password before storing", async () => {
      const userData = { email: "test@example.com", password: "plain123" };
      const user = await userService.createUser(userData);

      expect(user.password).not.toBe("plain123");
      expect(user.password).toMatch(/^\$argon2id\$/);
    });
  });
});
```

### Mocking

```typescript
// Mock external dependencies
jest.mock("./emailService");

it("should send welcome email after user creation", async () => {
  const emailService = require("./emailService");
  emailService.sendWelcomeEmail = jest.fn();

  await userService.createUser({ email: "test@example.com" });

  expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith(
    "test@example.com",
  );
});
```

## Integration Testing

### API Integration Tests

```typescript
import request from "supertest";
import { app } from "../app";

describe("POST /api/users", () => {
  beforeAll(async () => {
    await db.connect(); // Real database connection (test DB)
  });

  afterAll(async () => {
    await db.disconnect();
  });

  beforeEach(async () => {
    await db.users.deleteMany({}); // Clean state
  });

  it("should create user and return 201", async () => {
    const response = await request(app)
      .post("/api/users")
      .send({ email: "test@example.com", name: "Test User" })
      .expect(201);

    expect(response.body).toMatchObject({
      email: "test@example.com",
      name: "Test User",
    });

    // Verify database persistence
    const user = await db.users.findOne({ email: "test@example.com" });
    expect(user).toBeDefined();
  });

  it("should return 400 for invalid email", async () => {
    await request(app)
      .post("/api/users")
      .send({ email: "invalid-email", name: "Test" })
      .expect(400)
      .expect((res) => {
        expect(res.body.error).toBe("Invalid email format");
      });
  });
});
```

### Database Testing with TestContainers

```typescript
import { GenericContainer } from "testcontainers";

let container;
let db;

beforeAll(async () => {
  // Spin up real PostgreSQL in Docker
  container = await new GenericContainer("postgres:15")
    .withEnvironment({ POSTGRES_PASSWORD: "test" })
    .withExposedPorts(5432)
    .start();

  const port = container.getMappedPort(5432);
  db = await createConnection({
    host: "localhost",
    port,
    database: "test",
    password: "test",
  });
}, 60000);

afterAll(async () => {
  await container.stop();
});
```

## Contract Testing (Microservices)

### Pact (Consumer-Driven Contracts)

```typescript
// Consumer test
import { Pact } from "@pact-foundation/pact";

const provider = new Pact({
  consumer: "UserService",
  provider: "AuthService",
});

describe("Auth Service Contract", () => {
  beforeAll(() => provider.setup());
  afterEach(() => provider.verify());
  afterAll(() => provider.finalize());

  it("should validate user token", async () => {
    await provider.addInteraction({
      state: "user token exists",
      uponReceiving: "a request to validate token",
      withRequest: {
        method: "POST",
        path: "/auth/validate",
        headers: { "Content-Type": "application/json" },
        body: { token: "valid-token-123" },
      },
      willRespondWith: {
        status: 200,
        body: { valid: true, userId: "123" },
      },
    });

    const response = await authClient.validateToken("valid-token-123");
    expect(response.valid).toBe(true);
  });
});
```

## Load Testing

### Tools Comparison

**k6** (Modern, Developer-Friendly)

```javascript
import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  stages: [
    { duration: "2m", target: 100 }, // Ramp up to 100 users
    { duration: "5m", target: 100 }, // Stay at 100 users
    { duration: "2m", target: 0 }, // Ramp down to 0 users
  ],
  thresholds: {
    http_req_duration: ["p(95)<500"], // 95% requests under 500ms
  },
};

export default function () {
  const res = http.get("https://api.example.com/users");
  check(res, {
    "status is 200": (r) => r.status === 200,
    "response time < 500ms": (r) => r.timings.duration < 500,
  });
  sleep(1);
}
```

**Gatling** (JVM-based, Advanced Scenarios)
**JMeter** (GUI-based, Traditional)

### Performance Thresholds

- **Response time:** p95 < 500ms, p99 < 1s
- **Throughput:** 1000+ req/sec (target based on SLA)
- **Error rate:** < 1%
- **Concurrent users:** Test at 2x expected peak

## E2E Testing

### Playwright (Modern, Multi-Browser)

```typescript
import { test, expect } from "@playwright/test";

test("user can register and login", async ({ page }) => {
  // Navigate to registration page
  await page.goto("https://app.example.com/register");

  // Fill registration form
  await page.fill('input[name="email"]', "test@example.com");
  await page.fill('input[name="password"]', "SecurePass123!");
  await page.click('button[type="submit"]');

  // Verify redirect to dashboard
  await expect(page).toHaveURL("/dashboard");
  await expect(page.locator("h1")).toContainText("Welcome");

  // Verify API call was made
  const response = await page.waitForResponse("/api/users");
  expect(response.status()).toBe(201);
});
```

## Database Migration Testing

**Critical:** 83% migrations fail without proper testing

```typescript
describe("Database Migrations", () => {
  it("should migrate from v1 to v2 without data loss", async () => {
    // Insert test data in v1 schema
    await db.query(`
      INSERT INTO users (id, email, name)
      VALUES (1, 'test@example.com', 'Test User')
    `);

    // Run migration
    await runMigration("v2-add-created-at.sql");

    // Verify v2 schema
    const result = await db.query("SELECT * FROM users WHERE id = 1");
    expect(result.rows[0]).toMatchObject({
      id: 1,
      email: "test@example.com",
      name: "Test User",
      created_at: expect.any(Date),
    });
  });

  it("should rollback migration successfully", async () => {
    await runMigration("v2-add-created-at.sql");
    await rollbackMigration("v2-add-created-at.sql");

    // Verify v1 schema restored
    const columns = await db.query(`
      SELECT column_name FROM information_schema.columns
      WHERE table_name = 'users'
    `);
    expect(columns.rows.map((r) => r.column_name)).not.toContain("created_at");
  });
});
```

## Security Testing

### SAST (Static Application Security Testing)

```bash
# SonarQube for code quality + security
sonar-scanner \
  -Dsonar.projectKey=my-backend \
  -Dsonar.sources=src \
  -Dsonar.host.url=http://localhost:9000

# Semgrep for security patterns
semgrep --config auto src/
```

### DAST (Dynamic Application Security Testing)

```bash
# OWASP ZAP for runtime security scanning
docker run -t owasp/zap2docker-stable zap-baseline.py \
  -t https://api.example.com \
  -r zap-report.html
```

### Dependency Scanning (SCA)

```bash
# npm audit for Node.js
npm audit fix

# Snyk for multi-language
snyk test
snyk monitor  # Continuous monitoring
```

## Code Coverage

### Target Metrics (SonarQube Standards)

- **Overall coverage:** 80%+
- **Critical paths:** 100% (authentication, payment, data integrity)
- **New code:** 90%+

### Implementation

```bash
# Vitest with coverage
vitest run --coverage

# Jest with coverage
jest --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80}}'
```

## CI/CD Testing Pipeline

```yaml
# GitHub Actions example
name: Test Pipeline

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Unit Tests
        run: npm run test:unit

      - name: Integration Tests
        run: npm run test:integration

      - name: E2E Tests
        run: npm run test:e2e

      - name: Load Tests
        run: k6 run load-test.js

      - name: Security Scan
        run: npm audit && snyk test

      - name: Coverage Report
        run: npm run test:coverage

      - name: Upload to Codecov
        uses: codecov/codecov-action@v3
```

## Testing Best Practices

1. **Arrange-Act-Assert (AAA) Pattern**
2. **One assertion per test** (when practical)
3. **Descriptive test names** - `should throw error when email is invalid`
4. **Test edge cases** - Empty inputs, boundary values, null/undefined
5. **Clean test data** - Reset database state between tests
6. **Fast tests** - Unit tests < 10ms, Integration < 100ms
7. **Deterministic** - No flaky tests, avoid sleep(), use waitFor()
8. **Independent** - Tests don't depend on execution order

## Testing Checklist

- [ ] Unit tests cover 70% of codebase
- [ ] Integration tests for all API endpoints
- [ ] Contract tests for microservices
- [ ] Load tests configured (k6/Gatling)
- [ ] E2E tests for critical user flows
- [ ] Database migration tests
- [ ] Security scanning in CI/CD (SAST, DAST, SCA)
- [ ] Code coverage reports automated
- [ ] Tests run on every PR
- [ ] Flaky tests eliminated

## Resources

- **Vitest:** https://vitest.dev/
- **Playwright:** https://playwright.dev/
- **k6:** https://k6.io/docs/
- **Pact:** https://docs.pact.io/
- **TestContainers:** https://testcontainers.com/
