Skip to content

♻️ Make aiobotocore patch an autouse session fixture #173

Description

@sodre

Summary

The _patch_aiobotocore_response() function is called in ~90+ places across unit test files. Every test that creates a Repository or RateLimiter with moto must manually import and wrap with this function. It should be a single autouse session fixture applied automatically.

Problem

Current Implementation

After #170, the function lives in tests/fixtures/moto.py and is imported everywhere:

# tests/fixtures/moto.py
def _patch_aiobotocore_response():
    """Patch aiobotocore to work with moto's sync responses."""
    ...
# tests/unit/conftest.py — used in shared fixtures
from tests.fixtures.moto import _patch_aiobotocore_response

@pytest.fixture
async def limiter(mock_dynamodb):
    with _patch_aiobotocore_response():
        ...
# tests/unit/test_repository_builder.py — repeated per test method (~25 times in this file alone)
from tests.unit.conftest import _patch_aiobotocore_response

async def test_builder_basic(self, ...):
    with _patch_aiobotocore_response():
        repo = await Repository.builder(...).build()
        ...

async def test_builder_with_namespace(self, ...):
    with _patch_aiobotocore_response():
        repo = await Repository.builder(...).build()
        ...

Call sites by file

File _patch_aiobotocore_response calls
tests/unit/conftest.py 1 (shared limiter fixture)
tests/unit/test_limiter.py ~18
tests/unit/test_sync_limiter.py ~20
tests/unit/test_repository.py ~6
tests/unit/test_sync_repository.py ~6
tests/unit/test_repository_builder.py ~25
tests/unit/test_namespace_scoping.py 1
tests/unit/test_namespace_registry.py 1
tests/doctest/conftest.py 1
Total ~80-90

Issues

  1. Massive boilerplate: ~90 copies of from tests.unit.conftest import _patch_aiobotocore_response + with _patch_aiobotocore_response():
  2. Easy to forget: New tests must remember to apply the patch or get cryptic errors
  3. Noise in test code: 2 lines of boilerplate per test method obscure the actual test logic
  4. Re-import pattern: Most test methods import from conftest inside the method body rather than at module level

Why This Matters

Moto returns sync responses but aiobotocore expects async. Without this patch, tests fail with cryptic errors about awaiting non-awaitables deep in boto internals.

Proposed Solution

Convert to an autouse session-scoped fixture in tests/fixtures/moto.py:

# tests/fixtures/moto.py

@pytest.fixture(autouse=True, scope="session")
def patch_aiobotocore_for_moto():
    """Apply moto compatibility patch once for entire test session.

    Moto returns botocore.awsrequest.AWSResponse with sync content,
    but aiobotocore expects async content. This patch wraps the response
    handling to convert sync content to async.

    See: https://github.com/aio-libs/aiobotocore/discussions/1300

    Autouse ensures the patch is always applied for unit tests.
    Session-scoped because the patch is stateless and safe to share.
    """
    with _patch_aiobotocore_response():
        yield

Then register via conftest.py in directories that use moto:

# tests/unit/conftest.py
from tests.fixtures.moto import patch_aiobotocore_for_moto  # noqa: F401 (autouse)

Cleanup

Remove all ~90 instances of:

from tests.unit.conftest import _patch_aiobotocore_response

with _patch_aiobotocore_response():
    ...

The test body is simply dedented — no other changes needed.

Tasks

  • Add patch_aiobotocore_for_moto autouse session fixture to tests/fixtures/moto.py
  • Register it in tests/unit/conftest.py via import
  • Register it in tests/doctest/conftest.py via import
  • Remove all from tests.unit.conftest import _patch_aiobotocore_response imports from test methods
  • Remove all with _patch_aiobotocore_response(): wrappers and dedent test bodies
  • Verify all unit tests pass
  • Verify all doctest tests pass
  • Run hatch run generate-sync to regenerate sync test files (they contain _patch_aiobotocore_response calls too)

Acceptance Criteria

  • Patch applied automatically to all moto-based tests (unit + doctest)
  • Zero manual _patch_aiobotocore_response() calls remaining in test methods
  • All moto-based tests pass
  • Generated sync test files updated

Notes

  • Integration/E2E/benchmark tests using LocalStack do NOT need this patch (real async HTTP)
  • The _patch_aiobotocore_response() helper function stays in tests/fixtures/moto.py (the autouse fixture wraps it)
  • Some sync test files (test_sync_limiter.py, test_sync_repository.py) are generated — need to check if the async source files or the generator needs updating

Metadata

Metadata

Assignees

Labels

Type

Fields

No fields configured for Chore.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions