# Testing Requirements

Test-driven development standards and practices for Mosaic Stack.

## Testing Philosophy

- **Test-Driven Development (TDD)** — Write tests before implementation
- **Minimum 85% coverage** for all new code
- **All tests must pass** before PR approval
- **No untested code** in production

## Test Types

### Unit Tests

Test individual functions and methods in isolation.

**Location:** `*.spec.ts` next to source file

**Example:**

```typescript
// apps/api/src/auth/auth.service.spec.ts
describe("AuthService", () => {
  it("should create a session for valid user", async () => {
    const result = await authService.createSession(mockUser);
    expect(result.session.token).toBeDefined();
  });
});
```

### Integration Tests

Test interactions between components.

**Location:** `*.integration.spec.ts` in module directory

**Example:**

```typescript
// apps/api/src/auth/auth.integration.spec.ts
describe("Auth Integration", () => {
  it("should complete full login flow", async () => {
    const login = await request(app.getHttpServer())
      .post("/auth/sign-in")
      .send({ email, password });
    expect(login.status).toBe(200);
  });
});
```

### E2E Tests

Test complete user flows across the entire stack.

**Location:** `apps/web/tests/e2e/` (when implemented)

**Framework:** Playwright

## Running Tests

```bash
# All tests
pnpm test

# Watch mode (re-run on changes)
pnpm test:watch

# Coverage report
pnpm test:coverage

# Specific file
pnpm test apps/api/src/auth/auth.service.spec.ts

# API tests only
pnpm test:api

# E2E tests (when implemented)
pnpm test:e2e
```

## Writing Tests

### Structure

```typescript
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";

describe("ComponentName", () => {
  beforeEach(() => {
    // Setup
  });

  afterEach(() => {
    // Cleanup
  });

  describe("methodName", () => {
    it("should handle normal case", () => {
      // Arrange
      const input = "test";

      // Act
      const result = component.method(input);

      // Assert
      expect(result).toBe("expected");
    });

    it("should handle error case", () => {
      expect(() => component.method(null)).toThrow();
    });
  });
});
```

### Mocking

```typescript
// Mock service dependency
const mockPrismaService = {
  user: {
    findUnique: vi.fn(),
    create: vi.fn(),
  },
};

// Mock module
vi.mock("./some-module", () => ({
  someFunction: vi.fn(() => "mocked"),
}));
```

### Testing Async Code

```typescript
it("should complete async operation", async () => {
  const result = await asyncFunction();
  expect(result).toBeDefined();
});

// Or with resolves/rejects
it("should resolve with data", async () => {
  await expect(asyncFunction()).resolves.toBe("data");
});

it("should reject with error", async () => {
  await expect(failingFunction()).rejects.toThrow("Error");
});
```

## Coverage Requirements

### Minimum Coverage

- **Statements:** 85%
- **Branches:** 80%
- **Functions:** 85%
- **Lines:** 85%

### View Coverage Report

```bash
pnpm test:coverage

# Opens HTML report
open coverage/index.html
```

### Exemptions

Some code types may have lower coverage requirements:

- **DTOs/Interfaces:** No coverage required (type checking sufficient)
- **Constants:** No coverage required
- **Database migrations:** Manual verification acceptable

Always document exemptions in PR description.

## Best Practices

### 1. Test Behavior, Not Implementation

**❌ Bad:**

```typescript
it("should call getUserById", () => {
  service.login(email, password);
  expect(mockService.getUserById).toHaveBeenCalled();
});
```

**✅ Good:**

```typescript
it("should return session for valid credentials", async () => {
  const result = await service.login(email, password);
  expect(result.session.token).toBeDefined();
  expect(result.user.email).toBe(email);
});
```

### 2. Use Descriptive Test Names

**❌ Bad:**

```typescript
it('works', () => { ... });
it('test 1', () => { ... });
```

**✅ Good:**

```typescript
it('should return 401 for invalid credentials', () => { ... });
it('should create session with 24h expiration', () => { ... });
```

### 3. Arrange-Act-Assert Pattern

```typescript
it("should calculate total correctly", () => {
  // Arrange - Set up test data
  const items = [{ price: 10 }, { price: 20 }];

  // Act - Execute the code being tested
  const total = calculateTotal(items);

  // Assert - Verify the result
  expect(total).toBe(30);
});
```

### 4. Test Edge Cases

```typescript
describe("validateEmail", () => {
  it("should accept valid email", () => {
    expect(validateEmail("user@example.com")).toBe(true);
  });

  it("should reject empty string", () => {
    expect(validateEmail("")).toBe(false);
  });

  it("should reject null", () => {
    expect(validateEmail(null)).toBe(false);
  });

  it("should reject invalid format", () => {
    expect(validateEmail("notanemail")).toBe(false);
  });
});
```

### 5. Keep Tests Independent

```typescript
// ❌ Bad - Tests depend on order
let userId;
it("should create user", () => {
  userId = createUser();
});
it("should get user", () => {
  getUser(userId); // Fails if previous test fails
});

// ✅ Good - Each test is independent
it("should create user", () => {
  const userId = createUser();
  expect(userId).toBeDefined();
});
it("should get user", () => {
  const userId = createUser(); // Create fresh data
  const user = getUser(userId);
  expect(user).toBeDefined();
});
```

## CI/CD Integration

Tests run automatically on:

- Every push to feature branch
- Every pull request
- Before merge to `develop`

**Pipeline must pass** before merging.

## Debugging Tests

### Run Single Test

```typescript
it.only("should test specific case", () => {
  // Only this test runs
});
```

### Skip Test

```typescript
it.skip("should test something", () => {
  // This test is skipped
});
```

### Verbose Output

```bash
pnpm test --reporter=verbose
```

### Debug in VS Code

Add to `.vscode/launch.json`:

```json
{
  "type": "node",
  "request": "launch",
  "name": "Vitest",
  "runtimeExecutable": "pnpm",
  "runtimeArgs": ["test", "--no-coverage"],
  "console": "integratedTerminal"
}
```

## Test Database

Use separate test database:

```bash
# .env.test
TEST_DATABASE_URL=postgresql://mosaic:mosaic@localhost:5432/mosaic_test

# Reset test DB before each run
pnpm test:db:reset
```

## Next Steps

- **Review Commit Guidelines** — [Committing](3-committing.md)
- **Learn Database Testing** — [Database](../2-database/3-prisma.md)
- **Check Code Style** — [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)
