Quality
Comprehensive testing guidelines covering unit, integration, and end-to-end testing best practices.
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**
What this rule helps you achieve