testing-strategies

Complete framework for building effective, maintainable test suites that give you confidence to ship.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "testing-strategies" with this command: npx skills add sunnypatneedi/claude-starter-kit/sunnypatneedi-claude-starter-kit-testing-strategies

Testing Strategies

Complete framework for building effective, maintainable test suites that give you confidence to ship.

When to Use

  • Setting up testing for new projects

  • Improving existing test coverage

  • Practicing Test-Driven Development (TDD)

  • Debugging flaky tests

  • Deciding what to test (and what not to)

  • Organizing test files and structure

Core Testing Principles

Test Behavior, Not Implementation:

  • Test what the code does, not how

  • Tests should survive refactoring

  • Focus on public interfaces

Testing Pyramid:

     /\
    /  \         E2E (Few, slow, comprehensive)
   /----\
  /      \       Integration (Some, medium)
 /--------\
/          \     Unit (Many, fast, isolated)

/------------\

Write Tests That Fail Usefully:

  • Clear error messages

  • Pinpoint failure location

  • Show expected vs actual

Workflow

Step 1: Choose the Right Test Level

Unit Tests (70% of tests):

WHEN TO USE:

  • Business logic
  • Data transformations
  • Validation rules
  • Pure functions
  • Edge cases

CHARACTERISTICS:

  • Fast (<100ms each)
  • Isolated (no external dependencies)
  • Many tests
  • Run on every commit

Integration Tests (20% of tests):

WHEN TO USE:

  • API endpoints
  • Database operations
  • External service integration
  • Multiple components working together

CHARACTERISTICS:

  • Medium speed (100ms-1s each)
  • Real dependencies (test DB, APIs)
  • Fewer than unit tests
  • Run before merge

E2E Tests (10% of tests):

WHEN TO USE:

  • Critical user journeys
  • Complete workflows
  • Cross-browser compatibility

CHARACTERISTICS:

  • Slow (seconds per test)
  • Full application stack
  • Fewest tests
  • Run before release

Step 2: Write Unit Tests (AAA Pattern)

Arrange-Act-Assert:

describe('UserService', () => { describe('createUser', () => { it('creates a user with valid data', async () => { // ARRANGE: Set up test data and dependencies const userData = { email: 'test@example.com', name: 'Test User' }; const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) }; const service = new UserService(mockRepo);

  // ACT: Execute the code under test
  const result = await service.createUser(userData);

  // ASSERT: Verify the outcome
  expect(result).toEqual({ id: '1', ...userData });
  expect(mockRepo.save).toHaveBeenCalledWith(userData);
});

it('throws validation error for invalid email', async () => {
  // ARRANGE
  const userData = { email: 'invalid', name: 'Test' };
  const service = new UserService(mockRepo);

  // ACT &#x26; ASSERT
  await expect(service.createUser(userData))
    .rejects.toThrow('Invalid email format');
});

}); });

What to Unit Test:

✅ DO TEST:

  • Business logic
  • Data transformations
  • Validation rules
  • Edge cases (null, empty, max values)
  • Error conditions
  • Pure functions

❌ DON'T TEST:

  • Third-party libraries
  • Simple getters/setters
  • Framework code
  • Configuration
  • Trivial code

Mocking Guidelines:

// ✅ GOOD: Mock external dependencies const mockEmailService = { send: jest.fn().mockResolvedValue(true) };

const mockDatabase = { query: jest.fn().mockResolvedValue([{ id: 1 }]) };

// ✅ GOOD: Use dependency injection class UserService { constructor(private db: Database, private email: EmailService) {}

async createUser(data) { const user = await this.db.save(data); await this.email.send(user.email, 'Welcome!'); return user; } }

// In test: const service = new UserService(mockDb, mockEmail);

// ❌ BAD: Mocking internal implementation details // This couples tests to implementation

Step 3: Write Integration Tests

API Integration Tests:

describe('POST /api/users', () => { let db: Database;

beforeAll(async () => { db = await createTestDatabase(); });

afterAll(async () => { await db.close(); });

beforeEach(async () => { await db.query('DELETE FROM users'); });

it('creates a user and returns 201', async () => { const response = await request(app) .post('/api/users') .send({ email: 'test@example.com', name: 'Test' }) .expect(201);

expect(response.body).toMatchObject({
  id: expect.any(String),
  email: 'test@example.com'
});

// Verify in database
const user = await db.users.findOne({ email: 'test@example.com' });
expect(user).toBeDefined();

});

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

expect(response.body.error).toBe('Invalid email format');

});

it('returns 409 for duplicate email', async () => { await createUser({ email: 'test@example.com' });

await request(app)
  .post('/api/users')
  .send({ email: 'test@example.com', name: 'Test' })
  .expect(409);

}); });

Step 4: Write E2E Tests (Page Object Pattern)

Page Object:

// pages/LoginPage.ts class LoginPage { constructor(private page: Page) {}

async navigate() { await this.page.goto('/login'); }

async login(email: string, password: string) { await this.page.fill('[data-testid="email"]', email); await this.page.fill('[data-testid="password"]', password); await this.page.click('[data-testid="submit"]'); }

async getErrorMessage() { return this.page.textContent('[data-testid="error"]'); } }

// tests/login.spec.ts describe('Login', () => { it('logs in successfully with valid credentials', async () => { const loginPage = new LoginPage(page); await loginPage.navigate(); await loginPage.login('user@example.com', 'password');

await expect(page).toHaveURL('/dashboard');

});

it('shows error for invalid credentials', async () => { const loginPage = new LoginPage(page); await loginPage.navigate(); await loginPage.login('user@example.com', 'wrong');

const error = await loginPage.getErrorMessage();
expect(error).toBe('Invalid credentials');

}); });

E2E Best Practices:

✅ DO:

  • Use data-testid attributes
  • Test user journeys, not features
  • Run in realistic environment
  • Handle async properly
  • Clean up test data

❌ DON'T:

  • Test every edge case with E2E
  • Use brittle selectors (.class-name-xyz)
  • Share state between tests
  • Skip cleanup
  • Run E2E on every commit (too slow)

Step 5: Test-Driven Development (TDD)

Red-Green-Refactor Cycle:

  1. RED: Write a failing test └── Test describes desired behavior

  2. GREEN: Write minimal code to pass └── Just enough to make test pass

  3. REFACTOR: Clean up └── Improve code while tests pass

TDD Example:

// Step 1: RED - Write failing test describe('Cart', () => { it('calculates total price with discount', () => { const cart = new Cart(); cart.addItem({ price: 100, quantity: 2 }); cart.applyDiscount(10); // 10%

expect(cart.total).toBe(180); // (100 * 2) - 10%

}); });

// Run test: FAILS (Cart doesn't exist yet)

// Step 2: GREEN - Minimal implementation class Cart { items = []; discount = 0;

addItem(item) { this.items.push(item); }

applyDiscount(percent) { this.discount = percent; }

get total() { const subtotal = this.items.reduce( (sum, i) => sum + i.price * i.quantity, 0 ); return subtotal * (1 - this.discount / 100); } }

// Run test: PASSES

// Step 3: REFACTOR - Clean up class Cart { private items: CartItem[] = []; private discountPercent: number = 0;

addItem(item: CartItem): void { this.items.push(item); }

applyDiscount(percent: number): void { this.discountPercent = percent; }

get total(): number { const subtotal = this.calculateSubtotal(); return this.applyDiscountToSubtotal(subtotal); }

private calculateSubtotal(): number { return this.items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); }

private applyDiscountToSubtotal(subtotal: number): number { return subtotal * (1 - this.discountPercent / 100); } }

// Run test: STILL PASSES (refactoring didn't break anything)

Step 6: Test Coverage

Coverage Metrics:

LINE COVERAGE: % of lines executed ├── Easy to game ├── Doesn't ensure quality

BRANCH COVERAGE: % of decision branches ├── Better than line coverage ├── Catches missing else cases

MUTATION TESTING: % of mutations caught ├── Introduces bugs, checks if tests catch them ├── High quality metric ├── Slow to run

Coverage Guidelines:

Coverage Targets

TypeTargetPriority
Unit80%Business logic
Integration60%API endpoints
E2EKey flowsUser journeys

What Must Be Tested

  • All public API functions
  • All error handling paths
  • Business rule edge cases
  • Security-sensitive code

What Can Be Skipped

  • Configuration files
  • Generated code
  • Simple getters/setters
  • Third-party wrappers

Test Organization

File Structure:

src/ ├── users/ │ ├── user.service.ts │ ├── user.service.test.ts # Unit tests │ ├── user.repository.ts │ └── user.repository.test.ts ├── tests/ │ └── integration/ │ └── user.api.test.ts # Integration tests tests/ ├── e2e/ │ ├── login.spec.ts # E2E tests │ └── checkout.spec.ts ├── fixtures/ │ └── users.json # Test data └── setup.ts # Test configuration

Naming Conventions:

// Describe the unit being tested describe('UserService', () => { // Describe the method describe('createUser', () => { // Describe the scenario describe('when email is valid', () => { // State what it does it('creates a new user', () => {}); it('sends welcome email', () => {}); });

describe('when email already exists', () => {
  it('throws DuplicateEmailError', () => {});
});

}); });

Test Quality Checklist

Test Review Checklist

Structure

  • Tests follow AAA pattern
  • One assertion concept per test
  • Descriptive test names
  • Tests are independent
  • No test interdependence

Coverage

  • Happy path tested
  • Error cases tested
  • Edge cases tested
  • Boundary conditions tested

Maintainability

  • No flaky tests
  • Setup/teardown is minimal
  • Mocks are at boundaries
  • Tests survive refactoring

Performance

  • Unit tests <100ms each
  • Integration tests reasonable
  • E2E tests focused
  • Parallelization enabled

Common Testing Mistakes

Don't Do

Test implementation details Test public behavior

One assertion per test (too rigid) One concept per test

Copy-paste tests Use test helpers/factories

Share state between tests Isolate each test

Skip edge cases Test boundaries and errors

Mock everything Mock at boundaries only

Ignore flaky tests Fix or delete flaky tests

Write tests after code (always) Use TDD when beneficial

Tools & Frameworks

JavaScript/TypeScript:

  • Jest (unit + integration)

  • Vitest (fast Jest alternative)

  • Playwright (E2E)

  • Cypress (E2E)

  • Testing Library (React/Vue/etc)

Python:

  • pytest (unit + integration)

  • unittest (standard library)

  • Selenium (E2E)

Ruby:

  • RSpec (unit + integration)

  • Capybara (E2E)

Go:

  • testing package (standard library)

  • testify (assertions)

Related Skills

  • /code-review

  • Reviewing test quality

  • /devops-cicd

  • Running tests in CI/CD

  • /debugging

  • Using tests to find bugs

Last Updated: 2026-01-22

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

learning-coach

No summary provided by upstream source.

Repository SourceNeeds Review
General

sales-playbook

No summary provided by upstream source.

Repository SourceNeeds Review
General

daily-review

No summary provided by upstream source.

Repository SourceNeeds Review
General

productivity-gtd

No summary provided by upstream source.

Repository SourceNeeds Review