Quality

Testing Standards

Comprehensive testing guidelines covering unit, integration, and end-to-end testing best practices.

Cursor Add to Cursor
Cursor Rule (.mdc)

Copy this complete rule and save it as a .mdc file in your .cursor/rules directory

# Testing Standards

## Testing Pyramid

```
       /\
      /  \     E2E Tests (Few)
     /----\    - Critical user flows
    /      \   - Smoke tests
   /--------\  Integration Tests (Some)
  /          \ - API endpoints
 /            \ - Database operations
/--------------\ Unit Tests (Many)
                - Functions
                - Components
                - Utils
```

## Test Structure

### Arrange-Act-Assert (AAA)
```typescript
describe('UserService', () => {
  describe('createUser', () => {
    it('should create a user with valid data', async () => {
      // Arrange
      const userData = { name: 'John', email: '[email protected]' };

      // Act
      const user = await userService.createUser(userData);

      // Assert
      expect(user.id).toBeDefined();
      expect(user.name).toBe('John');
      expect(user.email).toBe('[email protected]');
    });
  });
});
```

### Naming Conventions
```typescript
// Format: should [expected behavior] when [condition]
it('should throw ValidationError when email is invalid', () => {});
it('should return empty array when no users exist', () => {});
it('should update user profile when data is valid', () => {});
```

## Unit Testing

### What to Test
- Pure functions and utilities
- Business logic
- Component rendering
- State management
- Error handling

### Mocking Best Practices
```typescript
// Mock external dependencies
jest.mock('./database', () => ({
  query: jest.fn()
}));

// Use dependency injection for easier testing
class UserService {
  constructor(private db: Database) {}

  async getUser(id: string) {
    return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
  }
}

// In test
const mockDb = { query: jest.fn() };
const service = new UserService(mockDb);
```

### Avoid Testing Implementation Details
```typescript
// BAD: Testing internal state
it('should set isLoading to true', () => {
  component.fetchData();
  expect(component.state.isLoading).toBe(true);
});

// GOOD: Testing behavior
it('should show loading spinner while fetching', () => {
  component.fetchData();
  expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
```

## Integration Testing

### API Testing
```typescript
describe('POST /api/users', () => {
  it('should create user and return 201', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'John', email: '[email protected]' })
      .expect(201);

    expect(response.body.data.id).toBeDefined();
    expect(response.body.data.name).toBe('John');
  });

  it('should return 400 for invalid email', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'John', email: 'invalid' })
      .expect(400);

    expect(response.body.error.code).toBe('VALIDATION_ERROR');
  });
});
```

### Database Testing
```typescript
describe('UserRepository', () => {
  beforeEach(async () => {
    await db.migrate.latest();
    await db.seed.run();
  });

  afterEach(async () => {
    await db.migrate.rollback();
  });

  it('should find user by email', async () => {
    const user = await userRepo.findByEmail('[email protected]');
    expect(user).toBeDefined();
    expect(user.email).toBe('[email protected]');
  });
});
```

## End-to-End Testing

### Critical Flows to Test
- User authentication (signup, login, logout)
- Core business workflows
- Payment flows
- Data creation and editing

### Playwright Example
```typescript
test('user can complete checkout', async ({ page }) => {
  // Login
  await page.goto('/login');
  await page.fill('[name="email"]', '[email protected]');
  await page.fill('[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  // Add item to cart
  await page.goto('/products/123');
  await page.click('button:has-text("Add to Cart")');

  // Checkout
  await page.goto('/checkout');
  await page.fill('[name="card"]', '4242424242424242');
  await page.click('button:has-text("Pay")');

  // Verify success
  await expect(page.locator('.success-message')).toBeVisible();
});
```

## Test Data Management

### Factories
```typescript
// Use factories for test data
const userFactory = {
  build: (overrides = {}) => ({
    id: faker.string.uuid(),
    name: faker.person.fullName(),
    email: faker.internet.email(),
    createdAt: new Date(),
    ...overrides
  })
};

// Usage
const user = userFactory.build({ name: 'Custom Name' });
```

### Fixtures
```typescript
// fixtures/users.json
{
  "adminUser": {
    "id": "1",
    "name": "Admin",
    "role": "admin"
  },
  "regularUser": {
    "id": "2",
    "name": "User",
    "role": "user"
  }
}
```

## Coverage Guidelines

### Target Coverage
- Statements: 80%+
- Branches: 75%+
- Functions: 80%+
- Lines: 80%+

### What Not to Obsess Over
- 100% coverage doesn't mean bug-free
- Focus on critical paths
- Don't test trivial code
- Generated code can be excluded

## CI/CD Integration

```yaml
# GitHub Actions example
test:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
    - run: npm ci
    - run: npm run test:unit
    - run: npm run test:integration
    - run: npm run test:e2e
    - uses: codecov/codecov-action@v3
```

## Best Practices Summary

1. **Test behavior, not implementation**
2. **Keep tests fast and independent**
3. **Use descriptive test names**
4. **One assertion per test (when practical)**
5. **Mock external dependencies**
6. **Don't test third-party code**
7. **Clean up after tests**
8. **Run tests in CI/CD pipeline**
9. **Review test code like production code**
10. **Maintain tests as features evolve**
Key Capabilities

What this rule helps you achieve

Test structure patternsMocking strategiesCoverage guidelinesCI/CD integration
Tags
testingunit-testsintegratione2e