agentleFS
mehdiozdemirmehdiozdemir/awesome-agent-skills/skills/test-generator/SKILL.md

test-generator

Skill2 starsChanged 10 months agoMITView on GitHub

Generates comprehensive test suites for code including unit tests, integration tests, and end-to-end tests. Use when creating tests for functions, classes, APIs, or entire modules, covering edge cases, error conditions, and ensuring high code coverage.

25 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use refactor-advisor, log-analyzer and 3 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • aidankaokaoSkill

    refactor-advisor

    分析程式碼的重構機會,給出有優先順序的建議,但不直接改寫程式碼。當使用者說「這段程式碼怎麼改比較好」、「幫我分析重構方向」、「這樣寫有什麼問題」、「refactor」、「技術債」時觸發。

    Also in 3 projects that use this

  • nek1987Skill

    log-analyzer

    Parse and analyze application logs to identify errors, patterns, and insights.

    Also in 3 projects that use this

  • JeffallanSkill

    security-reviewer

    Identifies security vulnerabilities, generates structured audit reports with severity ratings, and provides actionable remediation guidance. Use when conducting security audits, reviewing code for vulnerabilities, or analyzing infrastructure security. Invoke for SAST scans, penetration testing, DevSecOps practices, cloud security reviews, dependency audits, secrets scanning, or compliance checks. Produces vulnerability reports, prioritized recommendations, and compliance checklists.

    Also in 3 projects that use this

  • Microsoft AzureSkill

    git-workflow

    Squad branching model: dev-first workflow with insiders preview channel

    Also in 3 projects that use this

  • GoogleSkill

    code-review

    Adversarial systems, concurrency, memory-model, and architecture code review protocol. Use this skill whenever asked to review code, PRs, diffs, or branches in Filament.

    Also in 4 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. 0 are common, 16 are distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 15 edited versions we read keep these.

  • 16

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Assert: Verify the expected outcome

    line 18
  2. Distinctive:

    Independent: No dependencies between tests

    line 22
  3. Distinctive:

    Test single functions or methods in isolation

    line 37
  4. Distinctive:

    Use real dependencies (database, APIs)

    line 44
  5. Distinctive:

    Test complete user flows

    line 49
  6. Distinctive:

    Run against production-like environment

    line 50
  7. Distinctive:

    Create tests following language conventions:

    line 80
  8. Distinctive:

    Use meaningful data: - Avoid "foo", "bar" placeholders

    line 1199
  9. Distinctive:

    Keep tests independent: - No shared state between tests

    line 1200
  10. Distinctive:

    Test error paths: - Not just happy paths

    line 1201
  11. Distinctive:

    Write slow tests: - Keep unit tests fast

    line 1215
  12. Distinctive:

    When generating tests, provide:

    line 1247
  13. Distinctive:

    Update tests when requirements change

    line 1289
  14. Distinctive:

    Use CI/CD to enforce test passing

    line 1290
  15. Distinctive:

    Consider property-based testing for complex logic

    line 1291
  16. Distinctive:

    Write tests before fixing bugs (regression prevention)

    line 1292

“Common” is measured: kept by at least 6 in 10 of the 15 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Test Generator Skill

This skill creates thorough, maintainable test suites following testing best practices. Use this whenever you need to generate unit tests, integration tests, or end-to-end tests for any codebase.

Testing Principles

1. Test Structure (AAA Pattern)

Arrange → Act → Assert
  • Arrange: Set up test data and preconditions
  • Act: Execute the code under test
  • Assert: Verify the expected outcome

2. F.I.R.S.T. Principles

  • Fast: Tests should run quickly
  • Independent: No dependencies between tests
  • Repeatable: Same result every time
  • Self-validating: Clear pass/fail outcome
  • Timely: Written alongside production code

3. Test Coverage Goals

  • Happy path (expected behavior)
  • Edge cases (boundary conditions)
  • Error cases (exception handling)
  • Invalid inputs (validation)
  • Performance (when relevant)

Test Types

Unit Tests

  • Test single functions or methods in isolation
  • Mock external dependencies
  • Fast execution (milliseconds)
  • High coverage target (80%+)

Integration Tests

  • Test component interactions
  • Use real dependencies (database, APIs)
  • Medium execution time
  • Focus on critical paths

End-to-End Tests

  • Test complete user flows
  • Run against production-like environment
  • Slower execution
  • Cover business-critical scenarios

Test Generation Process

Step 1: Analyze the Code

  1. Identify the function/class to test
  2. Understand input parameters and types
  3. Determine expected outputs
  4. Identify dependencies to mock
  5. Note edge cases and error conditions

Step 2: Plan Test Cases

Systematically cover:

Input Categories:

  • Valid inputs (normal operation)
  • Boundary values (min, max, zero)
  • Invalid inputs (null, undefined, wrong type)
  • Empty inputs (empty string, empty array)
  • Special values (infinity, NaN, special chars)

State Categories:

  • Initial state
  • Populated state
  • Error state
  • Edge state

Step 3: Generate Tests

Create tests following language conventions:

  • Descriptive test names
  • Clear arrangement
  • Single assertion per test (when possible)
  • Proper cleanup

Step 4: Validate Coverage

Ensure tests cover:

  • All code paths
  • All branches
  • All error handlers
  • All public methods

Python Testing (pytest)

Basic Test Structure

import pytest
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime, timedelta

from myapp.services import UserService
from myapp.models import User
from myapp.exceptions import UserNotFoundError, ValidationError


class TestUserService:
    """Test suite for UserService class."""

    # ==================== Fixtures ====================

    @pytest.fixture
    def mock_db(self):
        """Create a mock database connection."""
        return Mock()

    @pytest.fixture
    def mock_cache(self):
        """Create a mock cache client."""
        return Mock()

    @pytest.fixture
    def user_service(self, mock_db, mock_cache):
        """Create UserService instance with mocked dependencies."""
        return UserService(db=mock_db, cache=mock_cache)

    @pytest.fixture
    def sample_user(self):
        """Create a sample user for testing."""
        return User(
            id="usr_123",
            email="test@example.com",
            name="Test User",
            created_at=datetime.now()
        )

    # ==================== Happy Path Tests ====================

    def test_get_user_by_id_returns_user_when_exists(
        self, user_service, mock_db, sample_user
    ):
        """Should return user when valid ID is provided."""
        # Arrange
        mock_db.find_one.return_value = sample_user

        # Act
        result = user_service.get_user_by_id("usr_123")

        # Assert
        assert result == sample_user
        mock_db.find_one.assert_called_once_with({"id": "usr_123"})

    def test_create_user_returns_new_user_with_generated_id(
        self, user_service, mock_db
    ):
        """Should create user with auto-generated ID."""
        # Arrange
        user_data = {"email": "new@example.com", "name": "New User"}
        mock_db.insert_one.return_value = {"id": "usr_456", **user_data}

        # Act
        result = user_service.create_user(user_data)

        # Assert
        assert result["id"].startswith("usr_")
        assert result["email"] == "new@example.com"

    def test_update_user_modifies_existing_user(
        self, user_service, mock_db, sample_user
    ):
        """Should update user and return modified user."""
        # Arrange
        mock_db.find_one.return_value = sample_user
        mock_db.update_one.return_value = True
        updates = {"name": "Updated Name"}

        # Act
        result = user_service.update_user("usr_123", updates)

        # Assert
        assert result["name"] == "Updated Name"

    # ==================== Edge Case Tests ====================

    def test_get_user_by_id_checks_cache_first(
        self, user_service, mock_db, mock_cache, sample_user
    ):
        """Should return cached user without hitting database."""
        # Arrange
        mock_cache.get.return_value = sample_user

        # Act
        result = user_service.get_user_by_id("usr_123")

        # Assert
        assert result == sample_user
        mock_cache.get.assert_called_once()
        mock_db.find_one.assert_not_called()

    def test_create_user_with_empty_name_uses_email_prefix(
        self, user_service, mock_db
    ):
        """Should use email prefix as name when name is empty."""
        # Arrange
        user_data = {"email": "john@example.com", "name": ""}
        mock_db.insert_one.return_value = {"id": "usr_789", **user_data}

        # Act
        result = user_service.create_user(user_data)

        # Assert
        assert result["name"] == "john"

    def test_get_users_with_pagination_respects_limit(
        self, user_service, mock_db
    ):
        """Should return only requested number of users."""
        # Arrange
        mock_db.find_many.return_value = [Mock() for _ in range(10)]

        # Act
        result = user_service.get_users(limit=10, offset=0)

        # Assert
        assert len(result) == 10
        mock_db.find_many.assert_called_with(limit=10, offset=0)

    # ==================== Error Case Tests ====================

    def test_get_user_by_id_raises_not_found_when_missing(
        self, user_service, mock_db
    ):
        """Should raise UserNotFoundError when user doesn't exist."""
        # Arrange
        mock_db.find_one.return_value = None

        # Act & Assert
        with pytest.raises(UserNotFoundError) as exc_info:
            user_service.get_user_by_id("usr_nonexistent")

        assert "usr_nonexistent" in str(exc_info.value)

    def test_create_user_raises_validation_error_on_invalid_email(
        self, user_service
    ):
        """Should raise ValidationError for malformed email."""
        # Arrange
        invalid_data = {"email": "not-an-email", "name": "Test"}

        # Act & Assert
        with pytest.raises(ValidationError) as exc_info:
            user_service.create_user(invalid_data)

        assert "email" in str(exc_info.value).lower()

    def test_create_user_raises_validation_error_on_duplicate_email(
        self, user_service, mock_db
    ):
        """Should raise ValidationError when email already exists."""
        # Arrange
        mock_db.find_one.return_value = {"email": "exists@example.com"}
        user_data = {"email": "exists@example.com", "name": "Test"}

        # Act & Assert
        with pytest.raises(ValidationError) as exc_info:
            user_service.create_user(user_data)

        assert "already exists" in str(exc_info.value).lower()

    def test_update_user_raises_not_found_for_missing_user(
        self, user_service, mock_db
    ):
        """Should raise UserNotFoundError when updating non-existent user."""
        # Arrange
        mock_db.find_one.return_value = None

        # Act & Assert
        with pytest.raises(UserNotFoundError):
            user_service.update_user("usr_missing", {"name": "New"})

    # ==================== Boundary Tests ====================

    def test_get_users_with_zero_limit_returns_empty_list(
        self, user_service, mock_db
    ):
        """Should return empty list when limit is 0."""
        # Act
        result = user_service.get_users(limit=0, offset=0)

        # Assert
        assert result == []

    def test_get_users_with_large_offset_returns_empty_list(
        self, user_service, mock_db
    ):
        """Should return empty list when offset exceeds total users."""
        # Arrange
        mock_db.find_many.return_value = []

        # Act
        result = user_service.get_users(limit=10, offset=10000)

        # Assert
        assert result == []

    def test_create_user_with_max_length_name_succeeds(
        self, user_service, mock_db
    ):
        """Should accept name at maximum length (255 chars)."""
        # Arrange
        long_name = "A" * 255
        user_data = {"email": "test@example.com", "name": long_name}
        mock_db.insert_one.return_value = {"id": "usr_123", **user_data}

        # Act
        result = user_service.create_user(user_data)

        # Assert
        assert len(result["name"]) == 255

    def test_create_user_with_exceeded_name_length_raises_error(
        self, user_service
    ):
        """Should reject name exceeding maximum length."""
        # Arrange
        too_long_name = "A" * 256
        user_data = {"email": "test@example.com", "name": too_long_name}

        # Act & Assert
        with pytest.raises(ValidationError):
            user_service.create_user(user_data)

    # ==================== Null/None Tests ====================

    def test_get_user_by_id_raises_error_on_none_id(self, user_service):
        """Should raise error when ID is None."""
        # Act & Assert
        with pytest.raises((ValidationError, TypeError)):
            user_service.get_user_by_id(None)

    def test_create_user_raises_error_on_none_input(self, user_service):
        """Should raise error when user data is None."""
        # Act & Assert
        with pytest.raises((ValidationError, TypeError)):
            user_service.create_user(None)


# ==================== Parametrized Tests ====================

class TestEmailValidation:
    """Test email validation with various inputs."""

    @pytest.mark.parametrize("email,expected_valid", [
        ("user@example.com", True),
        ("user.name@example.com", True),
        ("user+tag@example.com", True),
        ("user@subdomain.example.com", True),
        ("", False),
        ("notanemail", False),
        ("missing@domain", False),
        ("@nodomain.com", False),
        ("spaces in@email.com", False),
        (None, False),
    ])
    def test_email_validation(self, email, expected_valid):
        """Should correctly validate various email formats."""
        # Arrange
        from myapp.validators import is_valid_email

        # Act
        result = is_valid_email(email)

        # Assert
        assert result == expected_valid


class TestPasswordStrength:
    """Test password strength validation."""

    @pytest.mark.parametrize("password,expected_strength", [
        ("abc", "weak"),
        ("abcdefgh", "weak"),
        ("Abcdefgh", "medium"),
        ("Abcdefg1", "medium"),
        ("Abcdefg1!", "strong"),
        ("MyP@ssw0rd!", "strong"),
    ])
    def test_password_strength_levels(self, password, expected_strength):
        """Should correctly assess password strength."""
        # Arrange
        from myapp.validators import assess_password_strength

        # Act
        result = assess_password_strength(password)

        # Assert
        assert result == expected_strength


# ==================== Async Tests ====================

class TestAsyncUserService:
    """Test async methods of UserService."""

    @pytest.fixture
    def async_user_service(self):
        """Create async UserService instance."""
        return AsyncUserService()

    @pytest.mark.asyncio
    async def test_fetch_user_async_returns_user(self, async_user_service):
        """Should asynchronously fetch user."""
        # Arrange
        user_id = "usr_123"

        # Act
        result = await async_user_service.fetch_user(user_id)

        # Assert
        assert result["id"] == user_id

    @pytest.mark.asyncio
    async def test_fetch_multiple_users_concurrently(self, async_user_service):
        """Should fetch multiple users in parallel."""
        # Arrange
        user_ids = ["usr_1", "usr_2", "usr_3"]

        # Act
        results = await async_user_service.fetch_users(user_ids)

        # Assert
        assert len(results) == 3


# ==================== Integration Tests ====================

@pytest.mark.integration
class TestUserServiceIntegration:
    """Integration tests with real database."""

    @pytest.fixture(scope="class")
    def db_connection(self):
        """Create test database connection."""
        conn = create_test_database()
        yield conn
        cleanup_test_database(conn)

    def test_create_and_retrieve_user_flow(self, db_connection):
        """Should create user and retrieve with consistent data."""
        # Arrange
        service = UserService(db=db_connection)
        user_data = {"email": "integration@test.com", "name": "Test"}

        # Act
        created = service.create_user(user_data)
        retrieved = service.get_user_by_id(created["id"])

        # Assert
        assert retrieved["email"] == user_data["email"]
        assert retrieved["name"] == user_data["name"]

Pytest Configuration

# conftest.py
import pytest
from unittest.mock import Mock

@pytest.fixture(scope="session")
def app():
    """Create application instance for testing."""
    from myapp import create_app
    app = create_app(env="testing")
    return app

@pytest.fixture(scope="session")
def client(app):
    """Create test client."""
    return app.test_client()

@pytest.fixture(autouse=True)
def reset_mocks():
    """Reset all mocks between tests."""
    yield
    # Cleanup runs after each test

@pytest.fixture
def mock_external_api():
    """Mock external API calls."""
    with patch("myapp.clients.external_api") as mock:
        mock.fetch_data.return_value = {"status": "ok"}
        yield mock

# Markers for different test types
def pytest_configure(config):
    config.addinivalue_line("markers", "unit: Unit tests")
    config.addinivalue_line("markers", "integration: Integration tests")
    config.addinivalue_line("markers", "e2e: End-to-end tests")
    config.addinivalue_line("markers", "slow: Slow running tests")

JavaScript/TypeScript Testing (Jest/Vitest)

Basic Test Structure

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { UserService } from '../services/userService';
import { UserRepository } from '../repositories/userRepository';
import { CacheService } from '../services/cacheService';
import { UserNotFoundError, ValidationError } from '../errors';

// Mock dependencies
vi.mock('../repositories/userRepository');
vi.mock('../services/cacheService');

describe('UserService', () => {
  let userService: UserService;
  let mockUserRepo: jest.Mocked<UserRepository>;
  let mockCache: jest.Mocked<CacheService>;

  // ==================== Setup ====================

  beforeEach(() => {
    mockUserRepo = new UserRepository() as jest.Mocked<UserRepository>;
    mockCache = new CacheService() as jest.Mocked<CacheService>;
    userService = new UserService(mockUserRepo, mockCache);

    // Reset all mocks
    vi.clearAllMocks();
  });

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

  // ==================== Happy Path Tests ====================

  describe('getUserById', () => {
    it('should return user when valid ID is provided', async () => {
      // Arrange
      const expectedUser = {
        id: 'usr_123',
        email: 'test@example.com',
        name: 'Test User',
      };
      mockUserRepo.findById.mockResolvedValue(expectedUser);

      // Act
      const result = await userService.getUserById('usr_123');

      // Assert
      expect(result).toEqual(expectedUser);
      expect(mockUserRepo.findById).toHaveBeenCalledWith('usr_123');
      expect(mockUserRepo.findById).toHaveBeenCalledTimes(1);
    });

    it('should check cache before querying database', async () => {
      // Arrange
      const cachedUser = { id: 'usr_123', email: 'cached@example.com' };
      mockCache.get.mockResolvedValue(cachedUser);

      // Act
      const result = await userService.getUserById('usr_123');

      // Assert
      expect(result).toEqual(cachedUser);
      expect(mockCache.get).toHaveBeenCalledWith('user:usr_123');
      expect(mockUserRepo.findById).not.toHaveBeenCalled();
    });

    it('should cache user after fetching from database', async () => {
      // Arrange
      const user = { id: 'usr_123', email: 'test@example.com' };
      mockCache.get.mockResolvedValue(null);
      mockUserRepo.findById.mockResolvedValue(user);

      // Act
      await userService.getUserById('usr_123');

      // Assert
      expect(mockCache.set).toHaveBeenCalledWith(
        'user:usr_123',
        user,
        expect.any(Number)
      );
    });
  });

  // ==================== Error Case Tests ====================

  describe('getUserById - error cases', () => {
    it('should throw UserNotFoundError when user does not exist', async () => {
      // Arrange
      mockCache.get.mockResolvedValue(null);
      mockUserRepo.findById.mockResolvedValue(null);

      // Act & Assert
      await expect(userService.getUserById('usr_missing'))
        .rejects
        .toThrow(UserNotFoundError);
    });

    it('should throw ValidationError when ID is empty', async () => {
      // Act & Assert
      await expect(userService.getUserById(''))
        .rejects
        .toThrow(ValidationError);
    });

    it('should throw ValidationError when ID is null', async () => {
      // Act & Assert
      await expect(userService.getUserById(null as any))
        .rejects
        .toThrow(ValidationError);
    });

    it('should propagate database errors', async () => {
      // Arrange
      const dbError = new Error('Database connection failed');
      mockCache.get.mockResolvedValue(null);
      mockUserRepo.findById.mockRejectedValue(dbError);

      // Act & Assert
      await expect(userService.getUserById('usr_123'))
        .rejects
        .toThrow('Database connection failed');
    });
  });

  // ==================== Create User Tests ====================

  describe('createUser', () => {
    const validUserData = {
      email: 'new@example.com',
      name: 'New User',
      password: 'SecureP@ss123',
    };

    it('should create user with valid data', async () => {
      // Arrange
      const createdUser = { id: 'usr_456', ...validUserData };
      mockUserRepo.findByEmail.mockResolvedValue(null);
      mockUserRepo.create.mockResolvedValue(createdUser);

      // Act
      const result = await userService.createUser(validUserData);

      // Assert
      expect(result.id).toBeDefined();
      expect(result.email).toBe(validUserData.email);
      expect(mockUserRepo.create).toHaveBeenCalled();
    });

    it('should hash password before storing', async () => {
      // Arrange
      mockUserRepo.findByEmail.mockResolvedValue(null);
      mockUserRepo.create.mockImplementation(async (data) => ({
        id: 'usr_123',
        ...data,
      }));

      // Act
      await userService.createUser(validUserData);

      // Assert
      expect(mockUserRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          password: expect.not.stringMatching(validUserData.password),
        })
      );
    });

    it('should throw ValidationError for duplicate email', async () => {
      // Arrange
      mockUserRepo.findByEmail.mockResolvedValue({ id: 'existing' });

      // Act & Assert
      await expect(userService.createUser(validUserData))
        .rejects
        .toThrow(ValidationError);
    });

    it.each([
      ['missing email', { name: 'Test', password: 'Pass123!' }],
      ['missing name', { email: 'test@example.com', password: 'Pass123!' }],
      ['missing password', { email: 'test@example.com', name: 'Test' }],
      ['invalid email', { email: 'invalid', name: 'Test', password: 'Pass123!' }],
      ['weak password', { email: 'test@example.com', name: 'Test', password: 'abc' }],
    ])('should throw ValidationError for %s', async (_, invalidData) => {
      // Act & Assert
      await expect(userService.createUser(invalidData as any))
        .rejects
        .toThrow(ValidationError);
    });
  });

  // ==================== Boundary Tests ====================

  describe('boundary conditions', () => {
    it('should handle maximum name length (255 chars)', async () => {
      // Arrange
      const longName = 'A'.repeat(255);
      mockUserRepo.findByEmail.mockResolvedValue(null);
      mockUserRepo.create.mockResolvedValue({ id: 'usr_123' });

      // Act & Assert - should not throw
      await expect(userService.createUser({
        email: 'test@example.com',
        name: longName,
        password: 'SecureP@ss123',
      })).resolves.toBeDefined();
    });

    it('should reject name exceeding maximum length', async () => {
      // Arrange
      const tooLongName = 'A'.repeat(256);

      // Act & Assert
      await expect(userService.createUser({
        email: 'test@example.com',
        name: tooLongName,
        password: 'SecureP@ss123',
      })).rejects.toThrow(ValidationError);
    });

    it('should handle empty search results', async () => {
      // Arrange
      mockUserRepo.findMany.mockResolvedValue([]);

      // Act
      const result = await userService.searchUsers('nonexistent');

      // Assert
      expect(result).toEqual([]);
    });
  });
});

// ==================== Snapshot Tests ====================

describe('UserService snapshots', () => {
  it('should match user response structure', async () => {
    // Arrange
    const user = {
      id: 'usr_123',
      email: 'test@example.com',
      name: 'Test User',
      createdAt: new Date('2025-01-15'),
      role: 'user',
    };

    // Act
    const result = userService.formatUserResponse(user);

    // Assert
    expect(result).toMatchSnapshot();
  });
});

// ==================== Mock Timer Tests ====================

describe('UserService with timers', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

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

  it('should invalidate cache after TTL expires', async () => {
    // Arrange
    const user = { id: 'usr_123' };
    mockCache.get.mockResolvedValue(user);

    // Act
    await userService.getUserById('usr_123');
    vi.advanceTimersByTime(60 * 60 * 1000); // 1 hour

    // Assert
    expect(mockCache.delete).toHaveBeenCalledWith('user:usr_123');
  });
});

Jest Configuration

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src', '<rootDir>/tests'],
  testMatch: ['**/*.test.ts', '**/*.spec.ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/**/index.ts',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
};

Go Testing

Basic Test Structure

package user_test

import (
    "context"
    "errors"
    "testing"
    "time"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "github.com/stretchr/testify/require"
    "github.com/stretchr/testify/suite"

    "myapp/internal/user"
    "myapp/internal/user/mocks"
)

// ==================== Table-Driven Tests ====================

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name     string
        email    string
        wantErr  bool
        errType  error
    }{
        {
            name:    "valid email",
            email:   "user@example.com",
            wantErr: false,
        },
        {
            name:    "valid email with subdomain",
            email:   "user@mail.example.com",
            wantErr: false,
        },
        {
            name:    "empty email",
            email:   "",
            wantErr: true,
            errType: user.ErrInvalidEmail,
        },
        {
            name:    "missing @ symbol",
            email:   "userexample.com",
            wantErr: true,
            errType: user.ErrInvalidEmail,
        },
        {
            name:    "missing domain",
            email:   "user@",
            wantErr: true,
            errType: user.ErrInvalidEmail,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := user.ValidateEmail(tt.email)

            if tt.wantErr {
                assert.Error(t, err)
                if tt.errType != nil {
                    assert.ErrorIs(t, err, tt.errType)
                }
            } else {
                assert.NoError(t, err)
            }
        })
    }
}

// ==================== Test Suite ====================

type UserServiceTestSuite struct {
    suite.Suite
    service  *user.Service
    mockRepo *mocks.Repository
    mockCache *mocks.Cache
    ctx       context.Context
}

func (s *UserServiceTestSuite) SetupTest() {
    s.mockRepo = mocks.NewRepository(s.T())
    s.mockCache = mocks.NewCache(s.T())
    s.service = user.NewService(s.mockRepo, s.mockCache)
    s.ctx = context.Background()
}

func (s *UserServiceTestSuite) TearDownTest() {
    s.mockRepo.AssertExpectations(s.T())
    s.mockCache.AssertExpectations(s.T())
}

func TestUserServiceSuite(t *testing.T) {
    suite.Run(t, new(UserServiceTestSuite))
}

// ==================== Happy Path Tests ====================

func (s *UserServiceTestSuite) TestGetUserByID_Success() {
    // Arrange
    expectedUser := &user.User{
        ID:    "usr_123",
        Email: "test@example.com",
        Name:  "Test User",
    }
    s.mockCache.On("Get", s.ctx, "user:usr_123").Return(nil, nil)
    s.mockRepo.On("FindByID", s.ctx, "usr_123").Return(expectedUser, nil)
    s.mockCache.On("Set", s.ctx, "user:usr_123", expectedUser, mock.Anything).Return(nil)

    // Act
    result, err := s.service.GetUserByID(s.ctx, "usr_123")

    // Assert
    s.Require().NoError(err)
    s.Equal(expectedUser, result)
}

func (s *UserServiceTestSuite) TestGetUserByID_CacheHit() {
    // Arrange
    cachedUser := &user.User{ID: "usr_123", Email: "cached@example.com"}
    s.mockCache.On("Get", s.ctx, "user:usr_123").Return(cachedUser, nil)

    // Act
    result, err := s.service.GetUserByID(s.ctx, "usr_123")

    // Assert
    s.Require().NoError(err)
    s.Equal(cachedUser, result)
    s.mockRepo.AssertNotCalled(s.T(), "FindByID")
}

// ==================== Error Case Tests ====================

func (s *UserServiceTestSuite) TestGetUserByID_NotFound() {
    // Arrange
    s.mockCache.On("Get", s.ctx, "user:usr_missing").Return(nil, nil)
    s.mockRepo.On("FindByID", s.ctx, "usr_missing").Return(nil, user.ErrNotFound)

    // Act
    result, err := s.service.GetUserByID(s.ctx, "usr_missing")

    // Assert
    s.Nil(result)
    s.ErrorIs(err, user.ErrNotFound)
}

func (s *UserServiceTestSuite) TestGetUserByID_EmptyID() {
    // Act
    result, err := s.service.GetUserByID(s.ctx, "")

    // Assert
    s.Nil(result)
    s.ErrorIs(err, user.ErrInvalidID)
}

// ==================== Create User Tests ====================

func (s *UserServiceTestSuite) TestCreateUser_Success() {
    // Arrange
    input := user.CreateUserInput{
        Email:    "new@example.com",
        Name:     "New User",
        Password: "SecureP@ss123",
    }
    s.mockRepo.On("FindByEmail", s.ctx, input.Email).Return(nil, nil)
    s.mockRepo.On("Create", s.ctx, mock.AnythingOfType("*user.User")).Return(nil)

    // Act
    result, err := s.service.CreateUser(s.ctx, input)

    // Assert
    s.Require().NoError(err)
    s.NotEmpty(result.ID)
    s.Equal(input.Email, result.Email)
}

func (s *UserServiceTestSuite) TestCreateUser_DuplicateEmail() {
    // Arrange
    existingUser := &user.User{Email: "exists@example.com"}
    input := user.CreateUserInput{Email: "exists@example.com"}
    s.mockRepo.On("FindByEmail", s.ctx, input.Email).Return(existingUser, nil)

    // Act
    result, err := s.service.CreateUser(s.ctx, input)

    // Assert
    s.Nil(result)
    s.ErrorIs(err, user.ErrDuplicateEmail)
}

// ==================== Benchmark Tests ====================

func BenchmarkValidateEmail(b *testing.B) {
    email := "user@example.com"
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        _ = user.ValidateEmail(email)
    }
}

func BenchmarkHashPassword(b *testing.B) {
    password := "SecureP@ss123"
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        _, _ = user.HashPassword(password)
    }
}

// ==================== Integration Tests ====================

func TestUserService_Integration(t *testing.T) {
    if testing.Short() {
        t.Skip("Skipping integration test in short mode")
    }

    // Setup test database
    db := setupTestDB(t)
    defer cleanupTestDB(t, db)

    service := user.NewService(user.NewRepository(db), nil)
    ctx := context.Background()

    // Create user
    input := user.CreateUserInput{
        Email:    "integration@test.com",
        Name:     "Integration Test",
        Password: "TestP@ss123",
    }
    created, err := service.CreateUser(ctx, input)
    require.NoError(t, err)
    require.NotEmpty(t, created.ID)

    // Retrieve user
    retrieved, err := service.GetUserByID(ctx, created.ID)
    require.NoError(t, err)
    assert.Equal(t, input.Email, retrieved.Email)
    assert.Equal(t, input.Name, retrieved.Name)
}

API/HTTP Testing

Python (FastAPI + httpx)

import pytest
from httpx import AsyncClient
from fastapi import status

from myapp.main import app
from myapp.models import User


@pytest.fixture
def client():
    """Create async test client."""
    return AsyncClient(app=app, base_url="http://test")


class TestUserAPI:
    """Test suite for User API endpoints."""

    # ==================== GET /users/{id} ====================

    @pytest.mark.asyncio
    async def test_get_user_returns_200_for_existing_user(self, client):
        """GET /users/{id} should return 200 with user data."""
        # Act
        response = await client.get("/api/users/usr_123")

        # Assert
        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["id"] == "usr_123"
        assert "email" in data

    @pytest.mark.asyncio
    async def test_get_user_returns_404_for_missing_user(self, client):
        """GET /users/{id} should return 404 for non-existent user."""
        # Act
        response = await client.get("/api/users/usr_nonexistent")

        # Assert
        assert response.status_code == status.HTTP_404_NOT_FOUND
        assert response.json()["detail"] == "User not found"

    # ==================== POST /users ====================

    @pytest.mark.asyncio
    async def test_create_user_returns_201_with_valid_data(self, client):
        """POST /users should create user and return 201."""
        # Arrange
        payload = {
            "email": "new@example.com",
            "name": "New User",
            "password": "SecureP@ss123"
        }

        # Act
        response = await client.post("/api/users", json=payload)

        # Assert
        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()
        assert data["email"] == payload["email"]
        assert "id" in data
        assert "password" not in data  # Should not expose password

    @pytest.mark.asyncio
    async def test_create_user_returns_400_for_invalid_email(self, client):
        """POST /users should return 400 for invalid email."""
        # Arrange
        payload = {
            "email": "invalid-email",
            "name": "Test",
            "password": "SecureP@ss123"
        }

        # Act
        response = await client.post("/api/users", json=payload)

        # Assert
        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert "email" in response.json()["detail"].lower()

    @pytest.mark.asyncio
    async def test_create_user_returns_409_for_duplicate_email(self, client):
        """POST /users should return 409 for existing email."""
        # Arrange
        payload = {"email": "existing@example.com", "name": "Test", "password": "Pass123!"}

        # Act (create first user)
        await client.post("/api/users", json=payload)
        # Act (create duplicate)
        response = await client.post("/api/users", json=payload)

        # Assert
        assert response.status_code == status.HTTP_409_CONFLICT

    # ==================== Authentication Tests ====================

    @pytest.mark.asyncio
    async def test_protected_endpoint_returns_401_without_token(self, client):
        """Protected endpoints should return 401 without auth."""
        # Act
        response = await client.get("/api/users/me")

        # Assert
        assert response.status_code == status.HTTP_401_UNAUTHORIZED

    @pytest.mark.asyncio
    async def test_protected_endpoint_returns_200_with_valid_token(
        self, client, auth_token
    ):
        """Protected endpoints should work with valid token."""
        # Act
        response = await client.get(
            "/api/users/me",
            headers={"Authorization": f"Bearer {auth_token}"}
        )

        # Assert
        assert response.status_code == status.HTTP_200_OK

Testing Best Practices

DO ✅

  • One assertion per test - Focus on single behavior
  • Descriptive test names - Clearly state what is being tested
  • Follow AAA pattern - Arrange, Act, Assert
  • Test edge cases - Boundaries, nulls, empty values
  • Use meaningful data - Avoid "foo", "bar" placeholders
  • Keep tests independent - No shared state between tests
  • Test error paths - Not just happy paths
  • Use fixtures for common setup - DRY principle
  • Test behavior, not implementation - Focus on outcomes
  • Maintain test code quality - Tests are code too

DON'T ❌

  • Test implementation details - Focus on public API
  • Use random data - Unless testing randomness
  • Write flaky tests - Tests must be deterministic
  • Depend on test order - Tests should run in any order
  • Mock everything - Some integration is valuable
  • Ignore test failures - Fix or delete, never skip
  • Test third-party code - Focus on your code
  • Write slow tests - Keep unit tests fast
  • Duplicate test logic - Extract to helpers
  • Test trivial getters/setters - Focus on logic

Test Coverage Guidelines

Coverage Targets by Type

Test TypeCoverage Target
Unit Tests80%+ line coverage
Integration TestsCritical paths 100%
E2E TestsHappy paths + key edge cases

What to Prioritize

  1. Business-critical logic - Payment, auth, data integrity
  2. Complex algorithms - Sorting, calculations, transformations
  3. Edge cases - Boundaries, empty states, errors
  4. Public APIs - Entry points to your code
  5. Bug fixes - Prevent regressions

What NOT to Test

  1. Framework code - Trust your dependencies
  2. Configuration - Simple key-value mapping
  3. Generated code - ORM models, protos
  4. Third-party integrations - Mock instead
  5. Trivial code - Simple getters/setters

Output Format

When generating tests, provide:

## Test Strategy

**Testing Type:** [Unit / Integration / E2E]
**Framework:** [pytest / Jest / Go testing / etc.]
**Coverage Focus:** [What aspects are being tested]

---

## Generated Tests

[Complete test file with all test cases]

---

## Test Summary

| Category | Tests | Coverage |
|----------|-------|----------|
| Happy Path | X | Core functionality |
| Edge Cases | Y | Boundaries, special values |
| Error Cases | Z | Exception handling |
| Integration | W | Component interaction |

## Recommended Additional Tests

- [Test case 1 that should be added]
- [Test case 2 that should be added]

## Setup Required

- [Any fixtures or mocks needed]
- [Test database or external dependencies]

Notes

  • Adapt test depth to code complexity and criticality
  • Prioritize tests that catch real bugs
  • Balance coverage with maintainability
  • Update tests when requirements change
  • Use CI/CD to enforce test passing
  • Consider property-based testing for complex logic
  • Write tests before fixing bugs (regression prevention)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.