Claude Code Plugins

Community-maintained marketplace

Feedback

Testing strategy, patterns, and best practices for unit, integration, and e2e tests

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name testing
description Testing strategy, patterns, and best practices for unit, integration, and e2e tests

Testing Skill

Use this skill when writing tests, reviewing test coverage, or designing test strategies.

Test Types

Unit Tests

  • Test individual functions/methods in isolation.
  • Mock external dependencies (databases, APIs, filesystem).
  • Fast execution (milliseconds per test).
  • High volume (many tests covering many cases).

Integration Tests

  • Test interactions between components.
  • Use real dependencies or realistic fakes.
  • Test API contracts, database queries, external services.
  • Moderate execution time.

End-to-End (E2E) Tests

  • Test complete user workflows.
  • Run against a real or near-real environment.
  • Slowest to execute, most brittle.
  • Use sparingly for critical paths.

Test Pyramid

      /\
     /  \     E2E (few)
    /----\
   /      \   Integration (some)
  /--------\
 /          \ Unit (many)
/------------\

Aim for many unit tests, fewer integration tests, and minimal E2E tests.

Test Naming

Tests should clearly describe what they test and the expected outcome.

Pattern

test_<unit>_<scenario>_<expected_result>

Examples

# Python (pytest)
def test_login_with_valid_credentials_returns_token():
def test_login_with_invalid_password_raises_auth_error():
def test_calculate_total_with_empty_cart_returns_zero():
// TypeScript (vitest/jest)
describe('LoginForm', () => {
  it('submits successfully with valid credentials', () => {});
  it('displays error message with invalid password', () => {});
});
// Go
func TestLogin_ValidCredentials_ReturnsToken(t *testing.T) {}
func TestLogin_InvalidPassword_ReturnsError(t *testing.T) {}

Test Structure (AAA Pattern)

Every test should follow Arrange-Act-Assert:

def test_add_item_to_cart_increases_total():
    # Arrange - set up preconditions
    cart = Cart()
    item = Item(price=10.00)
    
    # Act - perform the action
    cart.add(item)
    
    # Assert - verify the outcome
    assert cart.total == 10.00

What to Test

Always Test

  • Happy path (expected inputs produce expected outputs)
  • Edge cases (empty, null, zero, max values)
  • Error conditions (invalid inputs, failures)
  • Boundary conditions (off-by-one, limits)

Edge Cases Checklist

  • Empty collections ([], {}, "")
  • Null/None/undefined values
  • Zero and negative numbers
  • Maximum values (MAX_INT, very long strings)
  • Unicode and special characters
  • Whitespace-only strings
  • Single-element collections
  • Duplicate values

Don't Test

  • Third-party library internals
  • Language features (operators, built-ins)
  • Private implementation details (test behavior, not implementation)
  • Trivial code (simple getters/setters)

Mocking Strategy

When to Mock

  • External services (APIs, databases, message queues)
  • Time-dependent code (use frozen time)
  • Randomness (use seeded generators)
  • Filesystem operations (in unit tests)
  • Expensive operations

When NOT to Mock

  • The unit under test
  • Simple data structures
  • Pure functions with no side effects

Mock Principles

  • Mock at the boundary, not deep in the stack
  • Verify mock interactions when behavior matters
  • Prefer fakes over mocks when possible
  • Reset mocks between tests

Test Quality Checklist

  • Tests are independent (no shared state, order doesn't matter)
  • Tests are deterministic (same result every run)
  • Tests are fast (slow tests get skipped)
  • Tests are readable (clear names, simple assertions)
  • Tests document behavior (readable as specifications)
  • Tests fail for the right reason (not false positives)
  • Tests cover edge cases, not just happy paths

Framework Patterns

Python (pytest)

import pytest

@pytest.fixture
def client():
    """Create test client."""
    return TestClient(app)

def test_get_user_returns_user(client):
    response = client.get("/users/1")
    assert response.status_code == 200
    assert response.json()["id"] == 1

@pytest.mark.parametrize("input,expected", [
    (0, 0),
    (1, 1),
    (5, 120),
])
def test_factorial(input, expected):
    assert factorial(input) == expected

TypeScript (vitest)

import { describe, it, expect, vi } from 'vitest';

describe('UserService', () => {
  it('fetches user by id', async () => {
    const mockFetch = vi.fn().mockResolvedValue({ id: 1, name: 'Test' });
    const service = new UserService(mockFetch);
    
    const user = await service.getUser(1);
    
    expect(user.name).toBe('Test');
    expect(mockFetch).toHaveBeenCalledWith('/users/1');
  });
});

Go

func TestGetUser(t *testing.T) {
    t.Run("returns user for valid ID", func(t *testing.T) {
        repo := &MockUserRepo{
            users: map[int]User{1: {ID: 1, Name: "Test"}},
        }
        service := NewUserService(repo)
        
        user, err := service.GetUser(1)
        
        if err != nil {
            t.Fatalf("unexpected error: %v", err)
        }
        if user.Name != "Test" {
            t.Errorf("got %q, want %q", user.Name, "Test")
        }
    })
}

Coverage Guidelines

  • Aim for meaningful coverage, not 100%
  • Focus on critical paths and complex logic
  • Low coverage in a file indicates undertested code
  • High coverage doesn't guarantee quality tests
  • Uncovered code should be a discussion point in review

Avoiding Flaky Tests

Flaky tests erode trust in the test suite. Prevent them:

Don't Use Sleep for Timing

# Bad - arbitrary sleep
time.sleep(2)
assert task.is_complete

# Good - poll with timeout
for _ in range(20):
    if task.is_complete:
        break
    time.sleep(0.1)
else:
    pytest.fail("Task did not complete in time")

Freeze Time for Time-Dependent Tests

# Python (freezegun)
from freezegun import freeze_time

@freeze_time("2024-01-15 10:00:00")
def test_expiration():
    token = create_token(expires_in=3600)
    assert token.expires_at == datetime(2024, 1, 15, 11, 0, 0)
// TypeScript (vitest)
import { vi } from 'vitest';

beforeEach(() => {
  vi.useFakeTimers();
  vi.setSystemTime(new Date('2024-01-15T10:00:00Z'));
});

afterEach(() => {
  vi.useRealTimers();
});

Use Fixed Seeds for Randomness

# Bad - random data each run
def test_shuffle():
    result = shuffle([1, 2, 3])
    assert len(result) == 3

# Good - seeded random for reproducibility
def test_shuffle():
    random.seed(42)
    result = shuffle([1, 2, 3])
    assert result == [2, 3, 1]

Isolate Test Data

  • Use unique identifiers per test to avoid collisions
  • Clean up test data after each test
  • Use database transactions that rollback
  • Don't rely on test execution order

Async Testing Patterns

Python (pytest-asyncio)

import pytest

@pytest.mark.asyncio
async def test_async_fetch():
    result = await fetch_user(1)
    assert result.id == 1

@pytest.fixture
async def async_client():
    async with AsyncClient(app) as client:
        yield client

@pytest.mark.asyncio
async def test_api_endpoint(async_client):
    response = await async_client.get("/users/1")
    assert response.status_code == 200

TypeScript (vitest)

it('handles async operations', async () => {
  await expect(fetchUser(1)).resolves.toEqual({ id: 1 });
  await expect(fetchUser(-1)).rejects.toThrow('Not found');
});

it('waits for UI updates', async () => {
  render(<UserProfile userId="1" />);
  
  // Wait for async content
  expect(await screen.findByText('Alice')).toBeInTheDocument();
});