Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    ricardoroche

    pytest-patterns

    ricardoroche/pytest-patterns
    Coding

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Automatically applies when writing pytest tests. Ensures proper use of fixtures, parametrize, marks, mocking, async tests, and follows testing best practices.

    SKILL.md

    Pytest Testing Pattern Enforcer

    When writing tests, follow these established pytest patterns and best practices.

    ✅ Basic Test Pattern

    import pytest
    from unittest.mock import Mock, patch, MagicMock
    
    def test_function_name_success():
        """Test successful operation."""
        # Arrange
        input_data = "test input"
    
        # Act
        result = function_under_test(input_data)
    
        # Assert
        assert result == expected_output
        assert result.status == "success"
    
    def test_function_name_error_case():
        """Test error handling."""
        # Arrange
        invalid_input = ""
    
        # Act & Assert
        with pytest.raises(ValueError, match="Input cannot be empty"):
            function_under_test(invalid_input)
    

    ✅ Async Test Pattern

    import pytest
    from unittest.mock import AsyncMock, patch
    
    @pytest.mark.asyncio
    async def test_async_function():
        """Test async function behavior."""
        # Arrange
        mock_data = {"id": "123", "name": "Test"}
    
        # Act
        result = await async_function()
    
        # Assert
        assert result == expected
        assert result["id"] == "123"
    
    @pytest.mark.asyncio
    @patch('module.async_dependency')
    async def test_with_async_mock(mock_dependency):
        """Test with mocked async dependency."""
        # Arrange
        mock_dependency.return_value = AsyncMock(return_value={"status": "ok"})
    
        # Act
        result = await function_calling_dependency()
    
        # Assert
        assert result["status"] == "ok"
        mock_dependency.assert_called_once()
    

    Fixtures

    import pytest
    
    # Function-scoped fixture (default)
    @pytest.fixture
    def user_data():
        """Provide test user data."""
        return {
            "id": "user_123",
            "email": "test@example.com",
            "name": "Test User"
        }
    
    # Session-scoped fixture (created once per test session)
    @pytest.fixture(scope="session")
    def database_connection():
        """Provide database connection for all tests."""
        db = Database.connect("test_db")
        yield db
        db.close()
    
    # Module-scoped fixture
    @pytest.fixture(scope="module")
    def api_client():
        """Provide API client for module tests."""
        client = APIClient(base_url="http://test.local")
        yield client
        client.close()
    
    # Fixture with cleanup (teardown)
    @pytest.fixture
    def temp_file(tmp_path):
        """Create temporary file for testing."""
        file_path = tmp_path / "test_file.txt"
        file_path.write_text("test content")
    
        yield file_path
    
        # Cleanup (runs after test)
        if file_path.exists():
            file_path.unlink()
    
    # Usage in tests
    def test_user_creation(user_data):
        """Test using fixture."""
        user = create_user(user_data)
        assert user.id == user_data["id"]
    

    Parametrize for Multiple Test Cases

    import pytest
    
    @pytest.mark.parametrize("input,expected", [
        ("hello", "HELLO"),
        ("world", "WORLD"),
        ("", ""),
        ("123", "123"),
    ])
    def test_uppercase(input, expected):
        """Test uppercase conversion with multiple inputs."""
        assert uppercase(input) == expected
    
    @pytest.mark.parametrize("email", [
        "user@example.com",
        "test.user@domain.co.uk",
        "user+tag@example.com",
    ])
    def test_valid_emails(email):
        """Test valid email formats."""
        assert is_valid_email(email) is True
    
    @pytest.mark.parametrize("email", [
        "invalid",
        "@example.com",
        "user@",
        "user@.com",
    ])
    def test_invalid_emails(email):
        """Test invalid email formats."""
        assert is_valid_email(email) is False
    
    # Multiple parameters
    @pytest.mark.parametrize("a,b,expected", [
        (1, 2, 3),
        (0, 0, 0),
        (-1, 1, 0),
        (100, 200, 300),
    ])
    def test_addition(a, b, expected):
        """Test addition with various inputs."""
        assert add(a, b) == expected
    
    # Named test cases
    @pytest.mark.parametrize("input,expected", [
        pytest.param("valid@email.com", True, id="valid_email"),
        pytest.param("invalid", False, id="invalid_email"),
        pytest.param("", False, id="empty_string"),
    ])
    def test_email_validation(input, expected):
        """Test email validation."""
        assert is_valid_email(input) == expected
    

    Mocking with unittest.mock

    from unittest.mock import Mock, MagicMock, patch, call
    
    def test_with_mock():
        """Test with Mock object."""
        mock_service = Mock()
        mock_service.get_data.return_value = {"status": "success"}
    
        result = process_data(mock_service)
    
        assert result["status"] == "success"
        mock_service.get_data.assert_called_once()
    
    def test_mock_multiple_calls():
        """Test multiple calls to mock."""
        mock = Mock()
        mock.side_effect = [1, 2, 3]  # Different return for each call
    
        assert mock() == 1
        assert mock() == 2
        assert mock() == 3
    
    @patch('module.external_api_call')
    def test_with_patch(mock_api):
        """Test with patched external call."""
        # Arrange
        mock_api.return_value = {"data": "test"}
    
        # Act
        result = function_that_calls_api()
    
        # Assert
        assert result["data"] == "test"
        mock_api.assert_called_once_with(expected_param)
    
    def test_mock_http_request():
        """Test HTTP request with mock response."""
        with patch('httpx.get') as mock_get:
            # Create mock response
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.json.return_value = {"key": "value"}
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response
    
            # Test
            result = fetch_data_from_api()
    
            assert result["key"] == "value"
            mock_get.assert_called_once()
    
    def test_verify_call_arguments():
        """Test that mock was called with specific arguments."""
        mock = Mock()
        function_with_mock(mock, param1="test", param2=123)
    
        # Verify call
        mock.method.assert_called_with("test", 123)
    
        # Verify any call in call history
        mock.method.assert_any_call("test", 123)
    
        # Verify call count
        assert mock.method.call_count == 1
    
        # Verify all calls
        mock.method.assert_has_calls([
            call("first"),
            call("second"),
        ])
    

    Pytest Marks

    import pytest
    
    # Skip test
    @pytest.mark.skip(reason="Not implemented yet")
    def test_future_feature():
        """Test to be implemented."""
        pass
    
    # Skip conditionally
    @pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")
    def test_python310_feature():
        """Test Python 3.10+ feature."""
        pass
    
    # Expected failure
    @pytest.mark.xfail(reason="Known bug in external library")
    def test_with_known_bug():
        """Test that currently fails due to known bug."""
        assert buggy_function() == expected
    
    # Custom marks
    @pytest.mark.slow
    def test_slow_operation():
        """Test that takes a long time."""
        pass
    
    @pytest.mark.integration
    def test_database_integration():
        """Integration test with database."""
        pass
    
    # Run with: pytest -m "not slow" to skip slow tests
    # Run with: pytest -m integration to run only integration tests
    

    Testing Exceptions

    import pytest
    
    def test_exception_raised():
        """Test that exception is raised."""
        with pytest.raises(ValueError):
            function_that_raises()
    
    def test_exception_message():
        """Test exception message."""
        with pytest.raises(ValueError, match="Invalid input"):
            function_that_raises("invalid")
    
    def test_exception_with_context():
        """Test exception with context checking."""
        with pytest.raises(APIError) as exc_info:
            call_failing_api()
    
        # Check exception details
        assert exc_info.value.status_code == 404
        assert "not found" in str(exc_info.value)
    

    Testing with Database (Fixtures)

    import pytest
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    
    @pytest.fixture(scope="session")
    def db_engine():
        """Create test database engine."""
        engine = create_engine("sqlite:///:memory:")
        Base.metadata.create_all(engine)
        yield engine
        engine.dispose()
    
    @pytest.fixture
    def db_session(db_engine):
        """Create database session for test."""
        Session = sessionmaker(bind=db_engine)
        session = Session()
    
        yield session
    
        session.rollback()
        session.close()
    
    def test_create_user(db_session):
        """Test user creation in database."""
        user = User(name="Test User", email="test@example.com")
        db_session.add(user)
        db_session.commit()
    
        # Verify
        found_user = db_session.query(User).filter_by(email="test@example.com").first()
        assert found_user is not None
        assert found_user.name == "Test User"
    

    Testing File Operations

    import pytest
    
    def test_file_read(tmp_path):
        """Test file reading with temporary file."""
        # Create temporary file
        test_file = tmp_path / "test.txt"
        test_file.write_text("test content")
    
        # Test
        result = read_file(test_file)
    
        assert result == "test content"
    
    def test_file_write(tmp_path):
        """Test file writing."""
        output_file = tmp_path / "output.txt"
    
        write_file(output_file, "new content")
    
        assert output_file.exists()
        assert output_file.read_text() == "new content"
    

    Test Organization

    # tests/test_user_service.py
    
    class TestUserService:
        """Tests for UserService."""
    
        def test_create_user_success(self):
            """Test successful user creation."""
            service = UserService()
            user = service.create_user("test@example.com")
            assert user.email == "test@example.com"
    
        def test_create_user_duplicate_email(self):
            """Test error on duplicate email."""
            service = UserService()
            service.create_user("test@example.com")
    
            with pytest.raises(DuplicateEmailError):
                service.create_user("test@example.com")
    
        def test_get_user_found(self):
            """Test getting existing user."""
            service = UserService()
            created = service.create_user("test@example.com")
    
            found = service.get_user(created.id)
    
            assert found.id == created.id
    
        def test_get_user_not_found(self):
            """Test getting non-existent user."""
            service = UserService()
    
            with pytest.raises(UserNotFoundError):
                service.get_user("nonexistent_id")
    

    Coverage

    # Run tests with coverage
    # pytest --cov=src --cov-report=html
    
    # Add to pyproject.toml
    [tool.pytest.ini_options]
    testpaths = ["tests"]
    python_files = ["test_*.py"]
    python_functions = ["test_*"]
    addopts = "--cov=src --cov-report=term-missing"
    
    # Minimum coverage requirement
    [tool.coverage.report]
    fail_under = 80
    exclude_lines = [
        "pragma: no cover",
        "def __repr__",
        "raise AssertionError",
        "raise NotImplementedError",
        "if __name__ == .__main__.:",
    ]
    

    ❌ Anti-Patterns

    # ❌ No test docstring
    def test_something():
        assert True
    
    # ❌ Testing multiple things in one test
    def test_user():
        # Too much in one test - split into multiple tests
        user = create_user()
        update_user(user)
        delete_user(user)
    
    # ❌ No arrange/act/assert structure
    def test_messy():
        result = function()
        x = 5
        assert result > x
        y = calculate()
    
    # ❌ Mutable fixture default
    @pytest.fixture
    def config():
        return {"key": "value"}  # Shared dict - mutations affect other tests!
    
    # ✅ Better
    @pytest.fixture
    def config():
        return {"key": "value"}.copy()
    
    # ❌ Not using parametrize
    def test_email1():
        assert is_valid("test@example.com")
    
    def test_email2():
        assert is_valid("user@domain.com")
    
    # ✅ Better: Use parametrize
    @pytest.mark.parametrize("email", ["test@example.com", "user@domain.com"])
    def test_valid_email(email):
        assert is_valid(email)
    
    # ❌ Not cleaning up resources
    def test_file():
        file = open("test.txt", "w")
        file.write("test")
        # Missing: file.close()
    
    # ✅ Better: Use context manager or fixture
    def test_file():
        with open("test.txt", "w") as file:
            file.write("test")
    

    Best Practices Checklist

    • ✅ Use descriptive test names: test_function_scenario_expectation
    • ✅ Add docstrings to all test functions
    • ✅ Follow Arrange/Act/Assert pattern
    • ✅ Use fixtures for setup and teardown
    • ✅ Use parametrize for multiple similar test cases
    • ✅ Use marks to categorize tests (slow, integration, etc.)
    • ✅ Mock external dependencies (APIs, databases)
    • ✅ Test both success and failure cases
    • ✅ Test edge cases (empty, null, boundary values)
    • ✅ One assertion focus per test (but multiple asserts OK)
    • ✅ Use tmp_path for file operations
    • ✅ Clean up resources (use fixtures with yield)
    • ✅ Aim for high coverage (80%+)
    • ✅ Keep tests independent (no shared state)

    Auto-Apply

    When writing tests:

    1. Use @pytest.mark.asyncio for async functions
    2. Use @patch decorator for mocking
    3. Create fixtures for common test data
    4. Follow naming conventions (test_*)
    5. Test success + error + edge cases
    6. Use parametrize for multiple inputs
    7. Add descriptive docstrings

    Related Skills

    • async-await-checker - For async test patterns
    • pydantic-models - For testing models
    • structured-errors - For testing error responses
    • pii-redaction - For testing PII handling
    Repository
    ricardoroche/ricardos-claude-code
    Files