# Test Strategy — Pyramids, Diamonds, and What to Test

<!-- hint:slides topic="Test strategy: test pyramid, what to test vs skip, test doubles, CI placement, and coverage tradeoffs" slides="5" -->

## Why Test?

Tests give you **confidence to change**. They document behavior, catch regressions, and make refactoring safe. Without tests, every change is a gamble.

## The Classic Test Pyramid

Martin Fowler popularized the **test pyramid**: many fast, cheap unit tests at the base; fewer integration tests in the middle; and few slow, expensive end-to-end (e2e) tests at the top.

```mermaid
flowchart TB
    subgraph E2E_layer[E2E - few, slow]
        E2E[E2E Tests]
    end
    subgraph INT_layer[Integration - some]
        INT[Integration Tests]
    end
    subgraph UNIT_layer[Unit - many, fast]
        UNIT[Unit Tests]
    end
    UNIT_layer --> INT_layer
    INT_layer --> E2E_layer
```

```mermaid
flowchart TB
    subgraph pyramid["Test Pyramid"]
        E2E["E2E Tests\n(slow, few)\nFull user flows"]
        INT["Integration Tests\n(medium)\nComponents + DB/API"]
        UNIT["Unit Tests\n(fast, many)\nSingle functions/classes"]
    end
    UNIT --> INT
    INT --> E2E
```

### Layer Characteristics

| Layer | What it tests | Speed | Isolation | Count |
|-------|---------------|-------|-----------|-------|
| **Unit** | Single function or class in isolation | Fast (ms) | High — mocks/stubs | Many |
| **Integration** | Components working together (DB, API, services) | Medium (seconds) | Medium — real deps | Some |
| **E2E** | Full user journey (UI → backend) | Slow (minutes) | Low — real everything | Few |

```javascript
// Unit: pure function, no I/O
function add(a, b) { return a + b; }
// Test: expect(add(2, 3)).toBe(5);

// Integration: hits DB or API
async function getUser(id) {
  const res = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  return res.rows[0];
}

// E2E: browser clicks → server → DB → response
// Playwright/Cypress: click "Login", fill form, assert dashboard
```

## The Testing Diamond (or Trophy)

Some teams invert the pyramid: more integration tests than unit tests. The **diamond** or **trophy** shape reflects that business logic often lives in integration layers. The idea: test what matters, where it matters.

```
       /\
      /  \   E2E (few)
     /----\
    /      \  Integration (many)
   /--------\
  /          \  Unit (some)
  -------------
```

## What to Test

| Focus | Examples |
|-------|----------|
| **Happy path** | User logs in with valid credentials → sees dashboard |
| **Edge cases** | Empty list, max length, boundary values |
| **Error paths** | Invalid input, network failure, permission denied |
| **Contracts** | API returns expected shape; component accepts valid props |

## What NOT to Test

- **Implementation details** — Don't assert on internal state that might change during refactoring.
- **Third-party code** — Trust libraries; mock them if needed.
- **Trivial code** — Getters/setters with no logic.
- **Everything** — Diminishing returns; prioritize risk.

## Test Doubles: Mocks, Stubs, Spies

| Type | Purpose |
|------|---------|
| **Stub** | Returns canned data; no verification |
| **Mock** | Verifies it was called (expectations) |
| **Spy** | Records calls; can assert after |

```javascript
// Stub: fake DB returns specific data
const stubDb = { query: () => ({ rows: [{ id: 1, name: 'Alice' }] }) };

// Mock: expect fetch to be called with specific URL
const mockFetch = vi.fn();
expect(mockFetch).toHaveBeenCalledWith('/api/users');
```

## Code Coverage

Coverage tells you **what you didn't test**. It's useful for finding gaps, but **100% coverage is not a goal**. Aim for meaningful coverage of critical paths; avoid testing for coverage's sake.

## Testing in CI/CD

- Run unit tests on every commit (fast feedback).
- Run integration tests on push to main.
- Run e2e tests on release branches or nightly.

```mermaid
flowchart LR
    A[Commit] --> B[Unit Tests]
    B --> C[Integration Tests]
    C --> D[E2E Tests]
    D --> E[Deploy]
```

---

## Key Takeaways

1. **Pyramid**: Many unit, some integration, few e2e.
2. **Test what matters**: Happy path, edge cases, error paths.
3. **Don't test**: Implementation details, third-party code.
4. **Use doubles** when you need isolation or speed.
5. **Coverage** is a metric, not a goal.
6. **CI/CD**: Fast tests first; gate deploys on passing tests.
