Agent skill

testing-strategies

Use when writing tests, setting up test frameworks, implementing mocking strategies, or establishing testing best practices (unit, integration, E2E) across any technology stack.

Stars 248
Forks 27

Install this agent skill to your Project

npx add-skill https://github.com/MadAppGang/claude-code/tree/main/plugins/dev/skills/core/testing-strategies

SKILL.md

Universal Testing Strategies

Overview

Language-agnostic testing patterns and strategies applicable across all technology stacks.

Testing Pyramid

          ┌───────┐
          │  E2E  │  Few, slow, expensive
         ─┴───────┴─
        ┌───────────┐
        │Integration│  Some, medium speed
       ─┴───────────┴─
      ┌───────────────┐
      │     Unit      │  Many, fast, cheap
     ─┴───────────────┴─

Unit Tests (70-80%)

  • Test individual functions/methods in isolation
  • Mock external dependencies
  • Fast execution (< 10ms per test)
  • High volume, run on every save

Integration Tests (15-20%)

  • Test component interactions
  • Real database/API calls (often with test containers)
  • Medium speed (100ms - 1s per test)
  • Run on commit/PR

End-to-End Tests (5-10%)

  • Test complete user flows
  • Real browser/environment
  • Slow (seconds to minutes)
  • Run before deployment

Test Structure: AAA Pattern

// Arrange - Set up test data and conditions
const user = createTestUser({ email: 'test@example.com' });
const service = new UserService(mockDb);

// Act - Execute the code under test
const result = await service.createUser(user);

// Assert - Verify the expected outcome
expect(result.id).toBeDefined();
expect(result.email).toBe('test@example.com');

Naming Conventions

Test File Names

component.test.ts       # Unit tests
component.spec.ts       # Alternative convention
component.integration.ts # Integration tests
component.e2e.ts        # End-to-end tests

Test Case Names

Use descriptive names that explain the scenario:

// Pattern: should [expected behavior] when [condition]
describe('UserService', () => {
  describe('createUser', () => {
    it('should create user when valid data provided', () => {});
    it('should throw ValidationError when email is invalid', () => {});
    it('should throw DuplicateError when email already exists', () => {});
  });
});

BDD Style (Given-When-Then)

describe('User Registration', () => {
  describe('given a valid email and password', () => {
    describe('when the user submits the form', () => {
      it('then creates a new account', () => {});
      it('then sends a welcome email', () => {});
    });
  });
});

Mocking Strategies

Test Doubles Overview

Type Purpose Example
Stub Returns predetermined values stub.returns(42)
Mock Verifies interactions expect(mock).toHaveBeenCalled()
Spy Wraps real implementation spy(realFunction)
Fake Working implementation (simplified) In-memory database
Dummy Placeholder (not used) Required parameter

When to Mock

DO Mock:

  • External services (APIs, databases)
  • Time-dependent functions
  • Random number generators
  • File system operations
  • Network requests

DON'T Mock:

  • The code under test
  • Simple value objects
  • Pure functions with no side effects

Mock Example

typescript
// Mock external API
const mockApi = {
  getUser: jest.fn().mockResolvedValue({ id: '1', name: 'Test' })
};

// Inject mock
const service = new UserService(mockApi);
const result = await service.getUser('1');

// Verify interaction
expect(mockApi.getUser).toHaveBeenCalledWith('1');
expect(result.name).toBe('Test');

Test Data Management

Test Factories

Create reusable factory functions for test data:

typescript
// factories/user.ts
export function createTestUser(overrides = {}) {
  return {
    id: randomUUID(),
    email: `test-${Date.now()}@example.com`,
    name: 'Test User',
    createdAt: new Date(),
    ...overrides
  };
}

// In tests
const user = createTestUser({ name: 'Custom Name' });

Fixtures

Static test data for consistent testing:

typescript
// fixtures/users.ts
export const validUser = {
  email: 'valid@example.com',
  password: 'SecurePass123!'
};

export const invalidEmails = [
  'no-at-sign',
  '@no-local.com',
  'no-domain@',
  'spaces in@email.com'
];

Assertion Best Practices

Be Specific

typescript
// BAD - vague assertion
expect(result).toBeTruthy();

// GOOD - specific assertion
expect(result.success).toBe(true);
expect(result.data.id).toBe('expected-id');

One Logical Assertion Per Test

typescript
// BAD - multiple unrelated assertions
it('should process order', () => {
  expect(order.id).toBeDefined();
  expect(order.total).toBe(100);
  expect(emailService.send).toHaveBeenCalled();
  expect(inventory.reduce).toHaveBeenCalled();
});

// GOOD - focused tests
it('should assign an order ID', () => {
  expect(order.id).toBeDefined();
});

it('should calculate correct total', () => {
  expect(order.total).toBe(100);
});

it('should send confirmation email', () => {
  expect(emailService.send).toHaveBeenCalled();
});

Custom Matchers

Create domain-specific matchers for readability:

typescript
expect.extend({
  toBeValidEmail(received) {
    const pass = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(received);
    return {
      pass,
      message: () => `expected ${received} to be a valid email`
    };
  }
});

// Usage
expect('user@example.com').toBeValidEmail();

Edge Cases to Test

Input Validation

  • Empty/null/undefined inputs
  • Boundary values (0, -1, MAX_INT)
  • Invalid types
  • Malformed data

State Transitions

  • Initial state
  • After single operation
  • After multiple operations
  • After error and recovery

Async Operations

  • Successful completion
  • Timeout
  • Network errors
  • Concurrent operations
  • Race conditions

Error Handling

  • Expected errors (validation)
  • Unexpected errors (system)
  • Error recovery
  • Error propagation

Code Coverage Guidelines

Coverage Targets

Type Target Notes
Line Coverage 80% Minimum acceptable
Branch Coverage 75% Test all conditionals
Function Coverage 90% All public APIs
Critical Paths 100% Auth, payments, data integrity

Coverage Pitfalls

High coverage ≠ good tests

typescript
// 100% coverage but useless test
it('should run without error', () => {
  processOrder(order); // No assertions!
});

Focus on:

  • Business-critical paths
  • Edge cases and error handling
  • Integration points

Test Performance

Keep Tests Fast

Test Type Target Time
Unit test < 10ms
Integration test < 1s
E2E test < 30s
Full suite < 5min

Parallelization

bash
# Run tests in parallel
vitest --pool=threads
jest --maxWorkers=4
pytest -n auto
go test -parallel 4

Test Isolation

Tests should not depend on each other:

typescript
// BAD - shared state
let counter = 0;
it('test 1', () => { counter++; });
it('test 2', () => { expect(counter).toBe(1); }); // Fragile!

// GOOD - isolated state
beforeEach(() => { counter = 0; });
it('test 1', () => { counter++; expect(counter).toBe(1); });
it('test 2', () => { counter++; expect(counter).toBe(1); });

CI/CD Integration

Pre-commit

  • Lint checks
  • Type checks
  • Unit tests (fast)

Pull Request

  • Full unit test suite
  • Integration tests
  • Coverage report

Pre-deployment

  • E2E tests
  • Performance tests
  • Security scans

Test Maintenance

Avoid Flaky Tests

  • Don't depend on timing
  • Don't depend on external services
  • Use deterministic data
  • Retry with care (hide real issues)

Keep Tests Readable

  • Clear naming
  • Minimal setup
  • Obvious assertions
  • Helpful failure messages

Review Test Code

  • Test code is production code
  • Apply same quality standards
  • Refactor when needed

Testing strategies applicable to all technology stacks

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results