---
description: Python Testing
alwaysApply: false
---

# Python Testing

pytest is the standard. Every behavior has a test. Every bug fix starts with a failing test.

## Test Structure (AAA)

```python
def test_user_creation() -> None:
    # Arrange
    repo = FakeUserRepo()
    service = UserService(repo)
    input_data = CreateUserInput(name="Alice", email="alice@example.com")
    # Act
    user = service.create(input_data)
    # Assert
    assert user.name == "Alice"
    assert user.id is not None
```

## Parametrize

```python
@pytest.mark.parametrize("input_str, expected", [
    ("hello world", "hello-world"),
    ("UPPER CASE", "upper-case"),
    ("", ""),
])
def test_slugify(input_str: str, expected: str) -> None:
    assert slugify(input_str) == expected
```

## Dependency Injection Over Mocking

```python
def test_create_user_sends_notification() -> None:
    notifier = Mock(spec=Notifier)
    service = UserService(FakeUserRepo(), notifier)
    service.create(CreateUserInput(name="Alice", email="a@b.com"))
    notifier.send_welcome.assert_called_once_with("a@b.com")

# When you must patch (third-party code)
@patch("mypackage.services.user_service.send_email")
def test_sends_email(mock_send: Mock) -> None:
    service.signup(email="test@example.com")
    mock_send.assert_called_once()
```

## Integration Testing

```python
@pytest.fixture
async def client(app) -> AsyncClient:
    async with AsyncClient(app=app, base_url="http://test") as ac:
        yield ac

@pytest.mark.asyncio
async def test_create_and_retrieve_user(client: AsyncClient) -> None:
    resp = await client.post("/users", json={"name": "Alice", "email": "a@b.com"})
    assert resp.status_code == 201
    resp = await client.get(f"/users/{resp.json()['id']}")
    assert resp.json()["name"] == "Alice"
```

## Anti-Patterns

```python
# Never: Tests that depend on execution order
# Never: Assertions without context
assert result  # What was expected? Use: assert result.status == "active"
# Never: Excessive mocking — if 5+ mocks, unit is too large
# Never: time.sleep() in tests — use polling or mock the clock
```
