---
name: Testing
description: Use this skill when writing tests, setting up test infrastructure, or implementing test-driven development. Covers unit, integration, and E2E testing.
version: 1.0.0
---

# Testing Skill

> For ensuring code quality through automated testing.

---

## When To Use This Skill

Use this skill when:
- Setting up testing infrastructure for a new project
- Writing tests for new features
- Adding tests for bug fixes (regression tests)
- Implementing test-driven development (TDD)
- Before merging code to ensure quality

---

## Testing Pyramid

```
         /\
        /E2E\         Few, slow, expensive
       /------\
      /Integration\   Some, medium speed
     /--------------\
    /   Unit Tests   \ Many, fast, cheap
   /------------------\
```

**Target Ratio:** ~70% Unit, ~20% Integration, ~10% E2E

---

## By Platform

### Web (Next.js/React)

#### Unit Tests - Vitest
```bash
npm install -D vitest @testing-library/react @testing-library/jest-dom
```

```typescript
// components/Button.test.tsx
import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders with label', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button')).toHaveTextContent('Click me');
  });

  it('calls onClick when clicked', async () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click</Button>);
    await userEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledOnce();
  });
});
```

#### Integration Tests - Testing Library
```typescript
// features/auth/login.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { LoginForm } from './LoginForm';

describe('LoginForm', () => {
  it('submits credentials and redirects', async () => {
    render(<LoginForm />);
    
    await userEvent.type(screen.getByLabelText('Email'), 'test@example.com');
    await userEvent.type(screen.getByLabelText('Password'), 'password');
    await userEvent.click(screen.getByRole('button', { name: 'Log in' }));
    
    await waitFor(() => {
      expect(screen.getByText('Welcome')).toBeInTheDocument();
    });
  });
});
```

#### E2E Tests - Playwright
```bash
npm install -D @playwright/test
```

```typescript
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';

test('user can sign up and log in', async ({ page }) => {
  await page.goto('/signup');
  
  await page.fill('[name="email"]', 'newuser@test.com');
  await page.fill('[name="password"]', 'SecurePass123');
  await page.click('button[type="submit"]');
  
  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Welcome')).toBeVisible();
});
```

---

### Mobile (React Native)

#### Unit Tests - Jest + React Native Testing Library
```bash
npm install -D jest @testing-library/react-native
```

```typescript
// components/Button.test.tsx
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';

describe('Button', () => {
  it('renders correctly', () => {
    const { getByText } = render(<Button title="Press me" />);
    expect(getByText('Press me')).toBeTruthy();
  });

  it('calls onPress when pressed', () => {
    const onPress = jest.fn();
    const { getByText } = render(<Button title="Press" onPress={onPress} />);
    fireEvent.press(getByText('Press'));
    expect(onPress).toHaveBeenCalled();
  });
});
```

#### E2E Tests - Detox
```typescript
// e2e/login.test.ts
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  it('should login successfully', async () => {
    await element(by.id('email-input')).typeText('test@example.com');
    await element(by.id('password-input')).typeText('password');
    await element(by.id('login-button')).tap();
    
    await expect(element(by.text('Welcome'))).toBeVisible();
  });
});
```

---

### macOS (SwiftUI)

#### Unit Tests - XCTest
```swift
// Tests/MyAppTests/AuthServiceTests.swift
import XCTest
@testable import MyApp

final class AuthServiceTests: XCTestCase {
    var authService: AuthService!
    
    override func setUp() {
        authService = AuthService()
    }
    
    func testValidEmailReturnsTrue() {
        XCTAssertTrue(authService.isValidEmail("test@example.com"))
    }
    
    func testInvalidEmailReturnsFalse() {
        XCTAssertFalse(authService.isValidEmail("invalid"))
    }
}
```

#### UI Tests - XCUITest
```swift
// UITests/LoginUITests.swift
import XCTest

final class LoginUITests: XCTestCase {
    let app = XCUIApplication()
    
    override func setUp() {
        app.launch()
    }
    
    func testLoginFlow() {
        app.textFields["Email"].tap()
        app.textFields["Email"].typeText("test@example.com")
        
        app.secureTextFields["Password"].tap()
        app.secureTextFields["Password"].typeText("password")
        
        app.buttons["Log In"].tap()
        
        XCTAssertTrue(app.staticTexts["Welcome"].exists)
    }
}
```

---

## What to Test

### Always Test
- [ ] Core business logic
- [ ] User authentication flows
- [ ] Payment processing
- [ ] Data validation
- [ ] Error handling paths

### Sometimes Test
- [ ] UI component rendering
- [ ] Integration between modules
- [ ] API response handling

### Rarely Test
- [ ] Third-party library internals
- [ ] Simple getter/setter methods
- [ ] Framework boilerplate

---

## Test-Driven Development (TDD)

### The Cycle

```
1. Write failing test (RED)
        ↓
2. Write minimal code to pass (GREEN)
        ↓
3. Refactor (REFACTOR)
        ↓
   Repeat
```

### Example TDD Session

```typescript
// Step 1: Write failing test
test('calculateDiscount returns 10% off for orders over $100', () => {
  expect(calculateDiscount(150)).toBe(15);
});

// Step 2: Make it pass (minimal)
function calculateDiscount(amount: number): number {
  if (amount > 100) return amount * 0.1;
  return 0;
}

// Step 3: Refactor if needed
const DISCOUNT_THRESHOLD = 100;
const DISCOUNT_RATE = 0.1;

function calculateDiscount(amount: number): number {
  if (amount > DISCOUNT_THRESHOLD) {
    return amount * DISCOUNT_RATE;
  }
  return 0;
}
```

---

## Test File Organization

```
src/
├── components/
│   ├── Button.tsx
│   └── Button.test.tsx      # Colocated tests
├── lib/
│   ├── utils.ts
│   └── utils.test.ts
├── features/
│   └── auth/
│       ├── login.tsx
│       └── login.test.tsx
tests/
├── integration/              # Integration tests
│   └── auth-flow.test.ts
└── e2e/                      # E2E tests
    └── signup.spec.ts
```

---

## CI/CD Integration

### GitHub Actions Example
```yaml
# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run test
      - run: npm run test:e2e
```

---

## Coverage Guidelines

| Coverage Level | When Appropriate |
|----------------|------------------|
| **80%+** | Production apps, payment systems |
| **60-80%** | MVPs, internal tools |
| **40-60%** | Prototypes, experiments |
| **Below 40%** | Proof of concepts only |

---

## MCP-Backed Test Execution

### Playwright MCP (Browser Testing)
The Playwright MCP server enables actual browser test execution, not just test writing:
- **E2E tests**: Navigate pages, fill forms, click buttons, assert outcomes
- **Accessibility testing**: Generate accessibility snapshots
- **Visual regression**: Compare screenshots across changes
- **Cross-browser**: Test in Chromium, Firefox, WebKit

**Integration:**
- Write tests using Playwright's API
- Execute via Playwright MCP for real browser interaction
- Use accessibility snapshots for component testing

### Property-Based Testing Triggers
(From Trail of Bits) Automatically suggest property-based tests when code contains:
| Code Pattern | PBT Opportunity | Priority |
|-------------|----------------|----------|
| Serialization + Deserialization pair | roundtrip(deserialize(serialize(x))) == x | HIGH |
| Parser / validator functions | Should never crash on arbitrary input | HIGH |
| Sorting / filtering | Output should maintain invariants | MEDIUM |
| State machines | Transitions should be valid from any reachable state | MEDIUM |
| Normalization functions | normalize(normalize(x)) == normalize(x) | MEDIUM |

---

## Rationalizations to Reject

| Rationalization | Why It's Wrong | Required Action |
|----------------|---------------|----------------|
| "This code is too simple to test" | Simple code that breaks is the hardest to debug | Write the test -- it takes 30 seconds |
| "I'll add tests later" | Later never comes, and you'll write weaker tests | Write tests NOW, before or alongside the code |
| "The type system catches these errors" | Types catch type errors, not logic errors or runtime issues | Test the behavior, not just the types |
| "Manual testing is sufficient" | Manual tests aren't repeatable and get skipped under pressure | Automate it |
| "Tests are slowing me down" | Tests slow you down NOW but save 10x the time in debugging LATER | Trust the process |
| "I don't know how to test this" | That's a skill gap, not an excuse | Learn the technique (mock, stub, spy, integration test) |
| "100% coverage is the goal" | Coverage measures lines hit, not behavior validated | Focus on meaningful tests, not coverage numbers |
| "The tests are flaky, so I'll skip them" | Flaky tests indicate a real problem (timing, state, dependencies) | Fix the flakiness, don't ignore it |

---

*This skill ensures reliability through systematic testing.*
