Skip to content

Testing Strategies for Pydapter

Protocol Testing

Protocol Compliance

from pydapter.protocols import Identifiable, Temporal

def test_protocol_compliance():
    model = MyModel(id=uuid4(), created_at=datetime.now(), updated_at=datetime.now())

    # Runtime protocol checks
    assert isinstance(model, Identifiable)
    assert isinstance(model, Temporal)

    # Test mixin functionality
    original_updated = model.updated_at
    model.update_timestamp()
    assert model.updated_at > original_updated

Adapter Testing

Roundtrip Testing

def test_adapter_roundtrip():
    """Test data survives roundtrip conversion"""
    original = MyModel(name="test", value=42)

    external = MyAdapter.to_obj(original)
    restored = MyAdapter.from_obj(MyModel, external)

    assert restored.name == original.name
    assert restored.value == original.value

Error Handling

def test_adapter_error_handling():
    """Test error scenarios"""
    with pytest.raises(ParseError, match="Invalid format"):
        MyAdapter.from_obj(MyModel, "invalid_data")

    with pytest.raises(ValidationError):
        MyAdapter.from_obj(MyModel, {"missing": "required_fields"})

Async Testing

@pytest.mark.asyncio
async def test_async_adapter(respx_mock):
    """Test async adapters with mocked HTTP"""
    respx_mock.get("http://api.example.com/data").mock(
        return_value=httpx.Response(200, json={"name": "test"})
    )

    result = await MyAsyncAdapter.from_obj(MyModel, {"url": "http://api.example.com/data"})
    assert result.name == "test"

Registry Testing

def test_registry_operations():
    """Test adapter registry functionality"""
    registry = AdapterRegistry()
    registry.register(MyAdapter)

    # Test retrieval
    adapter = registry.get("my_adapter")
    assert adapter == MyAdapter

    # Test missing adapter
    with pytest.raises(AdapterNotFoundError):
        registry.get("nonexistent")

Property-Based Testing

from hypothesis import given, strategies as st

@given(st.text(min_size=1))
def test_field_validation_robustness(text_value):
    """Test field validators with random data"""
    field = Field(name="test", validator=lambda cls, v: v.strip())
    # Test edge cases with generated data

Key Testing Patterns for LLM Developers

1. Test Fixtures

@pytest.fixture
def sample_user():
    return User(
        id=uuid4(),
        created_at=datetime.now(),
        updated_at=datetime.now(),
        name="Test User",
        email="test@example.com"
    )

@pytest.fixture
def user_registry():
    registry = AdapterRegistry()
    registry.register(JsonAdapter)
    registry.register(CsvAdapter)
    return registry

2. Mock External Dependencies

# HTTP APIs
@pytest.fixture
def mock_api(respx_mock):
    respx_mock.get("http://api.example.com/users").mock(
        return_value=httpx.Response(200, json=[{"name": "John", "age": 30}])
    )

# Database connections
@pytest.fixture
def mock_db():
    with patch('asyncpg.connect') as mock_connect:
        mock_conn = AsyncMock()
        mock_connect.return_value = mock_conn
        yield mock_conn

3. Error Path Testing

def test_all_error_scenarios():
    """Comprehensive error testing"""
    # Empty input
    with pytest.raises(ParseError, match="Empty.*content"):
        MyAdapter.from_obj(MyModel, "")

    # Invalid format
    with pytest.raises(ParseError, match="Invalid.*format"):
        MyAdapter.from_obj(MyModel, "invalid_format")

    # Validation failure
    with pytest.raises(ValidationError):
        MyAdapter.from_obj(MyModel, {"missing_required_field": True})

4. Async Testing Patterns

@pytest.mark.asyncio
class TestAsyncOperations:
    async def test_concurrent_operations(self):
        """Test concurrent adapter operations"""
        tasks = [
            MyAsyncAdapter.from_obj(MyModel, {"id": i})
            for i in range(10)
        ]
        results = await asyncio.gather(*tasks)
        assert len(results) == 10

    async def test_timeout_handling(self):
        """Test timeout scenarios"""
        with pytest.raises(ParseError, match="timed out"):
            await MyAsyncAdapter.from_obj(MyModel, config, timeout=0.01)

Common Testing Caveats

1. Async Context

  • Use pytest.mark.asyncio for async tests
  • Mock external services (HTTP, database)
  • Test timeout and retry logic

2. Protocol Mixins

  • Test both interface and implementation
  • Verify field serializers
  • Check inheritance order effects

3. Registry Isolation

  • Use fresh registries per test
  • Clean up registered adapters
  • Test adapter precedence

4. Error Context

  • Verify specific exception types
  • Check error message content
  • Test error data preservation

Testing Tips

  • Fixtures: Use pytest fixtures for common setups
  • Mocking: Mock external dependencies consistently
  • Error paths: Test failures as thoroughly as success
  • Property-based: Use Hypothesis for edge case discovery
  • Type safety: Run mypy in CI to catch type errors
  • Isolation: Ensure tests don't affect each other