[{"data":1,"prerenderedAt":2409},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002F":580,"surround-\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002F":2408},[4,186,386],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,42,66,90,114,150,174],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24,30,36],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Before, After and Wrap Validators in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Cross-Field Validation Patterns in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[47,48,54,60],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Discriminated Unions in OpenAPI with Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Examples in the OpenAPI Schema with FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Excluding Fields Per Endpoint in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"Self-Referencing and Recursive Models in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[95,96,102,108],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"model_construct and When to Skip Validation in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation\u002Findex",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"TypeAdapter for Non-Model Types in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types\u002Findex",[113],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[119,120,126,132,138,144],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Replacing json_encoders with field_serializer in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Migrating @root_validator to @model_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Request Validation Patterns in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Findex",[155,156,162,168],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Optional vs Nullable Fields in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Query, Path and Body Parameter Validation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002Findex",[167],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Validating File Uploads and Forms in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[179,180],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"Annotated Dependencies and Reusable Types in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types\u002Findex",[185],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[191,194,224,254,284,308,338,362],{"title":192,"path":188,"stem":193},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":195,"path":196,"stem":197,"children":198},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[199,200,206,212,218],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"Concurrent Requests with asyncio.gather in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[217],{"title":213,"path":214,"stem":215},{"title":219,"path":220,"stem":221,"children":222},"Running Sync Code in a Threadpool in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool\u002Findex",[223],{"title":219,"path":220,"stem":221},{"title":225,"path":226,"stem":227,"children":228},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[229,230,236,242,248],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Testing with Async Database Fixtures in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002Findex",[247],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Transaction Management and Rollback in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[259,260,266,272,278],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[265],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Retry and Idempotency for FastAPI Background Tasks","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks","async-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"When FastAPI BackgroundTasks Silently Fails","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails","async-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002Findex",[283],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[289,290,296,302],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Caching Dependency Results in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results","async-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002Findex",[301],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[313,314,320,326,332],{"title":309,"path":310,"stem":311},{"title":315,"path":316,"stem":317,"children":318},"Correlating Logs, Traces and Errors in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002Findex",[319],{"title":315,"path":316,"stem":317},{"title":321,"path":322,"stem":323,"children":324},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[325],{"title":321,"path":322,"stem":323},{"title":327,"path":328,"stem":329,"children":330},"Prometheus Metrics for FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002Findex",[331],{"title":327,"path":328,"stem":329},{"title":333,"path":334,"stem":335,"children":336},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[337],{"title":333,"path":334,"stem":335},{"title":339,"path":340,"stem":341,"children":342},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[343,344,350,356],{"title":339,"path":340,"stem":341},{"title":345,"path":346,"stem":347,"children":348},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[349],{"title":345,"path":346,"stem":347},{"title":351,"path":352,"stem":353,"children":354},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[355],{"title":351,"path":352,"stem":353},{"title":357,"path":358,"stem":359,"children":360},"Rate Limit Headers and 429 Responses in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses","async-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses\u002Findex",[361],{"title":357,"path":358,"stem":359},{"title":363,"path":364,"stem":365,"children":366},"Testing FastAPI Applications","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Findex",[367,368,374,380],{"title":363,"path":364,"stem":365},{"title":369,"path":370,"stem":371,"children":372},"Mocking External Services in FastAPI Tests","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002Findex",[373],{"title":369,"path":370,"stem":371},{"title":375,"path":376,"stem":377,"children":378},"TestClient vs httpx AsyncClient in FastAPI","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002Findex",[379],{"title":375,"path":376,"stem":377},{"title":381,"path":382,"stem":383,"children":384},"Testing Async FastAPI Endpoints with pytest-asyncio","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002Findex",[385],{"title":381,"path":382,"stem":383},{"title":387,"path":388,"stem":389,"children":390},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[391,394,412,436,472,496,526,556],{"title":392,"path":388,"stem":393},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":395,"path":396,"stem":397,"children":398},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[399,400,406],{"title":395,"path":396,"stem":397},{"title":401,"path":402,"stem":403,"children":404},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[405],{"title":401,"path":402,"stem":403},{"title":407,"path":408,"stem":409,"children":410},"Lifespan Events vs Startup and Shutdown in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown\u002Findex",[411],{"title":407,"path":408,"stem":409},{"title":413,"path":414,"stem":415,"children":416},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[417,418,424,430],{"title":413,"path":414,"stem":415},{"title":419,"path":420,"stem":421,"children":422},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[423],{"title":419,"path":420,"stem":421},{"title":425,"path":426,"stem":427,"children":428},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[429],{"title":425,"path":426,"stem":427},{"title":431,"path":432,"stem":433,"children":434},"Secrets and .env Files Per Environment in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment\u002Findex",[435],{"title":431,"path":432,"stem":433},{"title":437,"path":438,"stem":439,"children":440},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[441,442,448,454,460,466],{"title":437,"path":438,"stem":439},{"title":443,"path":444,"stem":445,"children":446},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[447],{"title":443,"path":444,"stem":445},{"title":449,"path":450,"stem":451,"children":452},"Dependency Caching and use_cache in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache\u002Findex",[453],{"title":449,"path":450,"stem":451},{"title":455,"path":456,"stem":457,"children":458},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[459],{"title":455,"path":456,"stem":457},{"title":461,"path":462,"stem":463,"children":464},"Overriding Dependencies in FastAPI Tests","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002Findex",[465],{"title":461,"path":462,"stem":463},{"title":467,"path":468,"stem":469,"children":470},"Yield Dependencies and Cleanup Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order\u002Findex",[471],{"title":467,"path":468,"stem":469},{"title":473,"path":474,"stem":475,"children":476},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[477,478,484,490],{"title":473,"path":474,"stem":475},{"title":479,"path":480,"stem":481,"children":482},"Customising Validation Error Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002Findex",[483],{"title":479,"path":480,"stem":481},{"title":485,"path":486,"stem":487,"children":488},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[489],{"title":485,"path":486,"stem":487},{"title":491,"path":492,"stem":493,"children":494},"HTTPException vs Custom Exception Classes in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes\u002Findex",[495],{"title":491,"path":492,"stem":493},{"title":497,"path":498,"stem":499,"children":500},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[501,502,508,514,520],{"title":497,"path":498,"stem":499},{"title":503,"path":504,"stem":505,"children":506},"CORS Middleware Configuration in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration\u002Findex",[507],{"title":503,"path":504,"stem":505},{"title":509,"path":510,"stem":511,"children":512},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[513],{"title":509,"path":510,"stem":511},{"title":515,"path":516,"stem":517,"children":518},"Middleware Execution Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002Findex",[519],{"title":515,"path":516,"stem":517},{"title":521,"path":522,"stem":523,"children":524},"Middleware vs Dependencies: When to Use Which","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which\u002Findex",[525],{"title":521,"path":522,"stem":523},{"title":527,"path":528,"stem":529,"children":530},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[531,532,538,544,550],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541,"children":542},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[543],{"title":539,"path":540,"stem":541},{"title":545,"path":546,"stem":547,"children":548},"Router Tags and OpenAPI Grouping in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping\u002Findex",[549],{"title":545,"path":546,"stem":547},{"title":551,"path":552,"stem":553,"children":554},"Versioning APIs with FastAPI Routers","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002Findex",[555],{"title":551,"path":552,"stem":553},{"title":557,"path":558,"stem":559,"children":560},"The FastAPI Request\u002FResponse Lifecycle","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Findex",[561,562,568,574],{"title":557,"path":558,"stem":559},{"title":563,"path":564,"stem":565,"children":566},"How a Request Flows Through FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi\u002Findex",[567],{"title":563,"path":564,"stem":565},{"title":569,"path":570,"stem":571,"children":572},"Response Model and Serialization Order","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002Findex",[573],{"title":569,"path":570,"stem":571},{"title":575,"path":576,"stem":577,"children":578},"Streaming and File Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002Findex",[579],{"title":575,"path":576,"stem":577},{"id":581,"title":369,"body":582,"dateModified":2377,"datePublished":2377,"description":2378,"extension":2379,"faq":2380,"howto":2391,"meta":2392,"navigation":863,"path":370,"seo":2405,"stem":371,"type":2406,"__hash__":2407},"content\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002Findex.md",{"type":583,"value":584,"toc":2367},"minimark",[585,589,596,632,640,770,775,778,785,795,811,817,821,831,1474,1477,1480,1487,1498,1505,1567,1587,1591,1597,1687,1706,1709,1713,1726,2010,2013,2019,2022,2028,2038,2044,2128,2131,2135,2142,2247,2250,2253,2257,2263,2266,2281,2285,2291,2297,2303,2309,2315,2319,2363],[586,587,369],"h1",{"id":588},"mocking-external-services-in-fastapi-tests",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,617,623,626,629],"ul",{},[600,601,602,603,607,608,612,613,616],"li",{},"Dependency overrides replace a ",[604,605,606],"em",{},"collaborator","; ",[609,610,611],"code",{},"MockTransport"," replaces the ",[604,614,615],{},"socket",". Pick by which layer you want tested.",[600,618,619,622],{},[609,620,621],{},"app.dependency_overrides"," is keyed by the callable, so it applies everywhere the dependency is declared, including nested.",[600,624,625],{},"Monkeypatching binds to an import path and breaks silently when the import moves.",[600,627,628],{},"Always clear overrides in fixture teardown — the dict lives on the app and outlives the test.",[600,630,631],{},"A double that never raises leaves your error handling untested; make the double fail too.",[590,633,634,635,639],{},"Your test suite either calls a live third-party API or fails in CI because it cannot. This page, part of ",[636,637,363],"a",{"href":638},"\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002F",", compares the three ways to cut that dependency and shows real responses from each.",[641,642,643,766],"figure",{},[644,645,653,654,653,658,653,662,653,671,653,678,653,682,653,687,653,690,653,694,653,699,653,703,653,710,653,715,653,719,653,722,653,726,653,729,653,734,653,738,653,742,653,747,653,751,653,754,653,758,653,761,653,763],"svg",{"viewBox":646,"role":647,"ariaLabelledBy":648,"xmlns":651,"style":652},"0 0 720 280","img",[649,650],"mock-title","mock-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[655,656,657],"title",{"id":649},"Where each mocking strategy cuts the call chain",[659,660,661],"desc",{"id":650},"A chain from endpoint to client class to httpx transport to network, with dependency override cutting at the client, monkeypatch cutting at the module attribute, and MockTransport cutting at the transport.",[663,664],"rect",{"x":665,"y":666,"width":667,"height":668,"rx":669,"style":670},"20","40","150","52","8","fill:#00796B;stroke:#00796B;stroke-width:1.4px",[672,673,677],"text",{"x":674,"y":675,"style":676},"95","72","text-anchor:middle;fill:#ffffff;font:600 13px sans-serif","endpoint",[663,679],{"x":680,"y":666,"width":667,"height":668,"rx":669,"style":681},"200","fill:none;stroke:currentColor;stroke-width:1.4px",[672,683,686],{"x":684,"y":675,"style":685},"275","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","client class",[663,688],{"x":689,"y":666,"width":667,"height":668,"rx":669,"style":681},"380",[672,691,693],{"x":692,"y":675,"style":685},"455","httpx transport",[663,695],{"x":696,"y":666,"width":697,"height":668,"rx":669,"style":698},"560","140","fill:none;stroke:currentColor;stroke-width:1.4px;stroke-dasharray:6 4",[672,700,702],{"x":701,"y":675,"style":685},"630","network",[704,705],"line",{"x1":706,"y1":707,"x2":708,"y2":707,"style":709},"170","66","192","stroke:currentColor;stroke-width:1.4px",[711,712],"polygon",{"points":713,"style":714},"192,62 200,66 192,70","fill:currentColor",[704,716],{"x1":717,"y1":707,"x2":718,"y2":707,"style":709},"350","372",[711,720],{"points":721,"style":714},"372,62 380,66 372,70",[704,723],{"x1":724,"y1":707,"x2":725,"y2":707,"style":709},"530","552",[711,727],{"points":728,"style":714},"552,62 560,66 552,70",[704,730],{"x1":731,"y1":732,"x2":731,"y2":697,"style":733},"185","30","stroke:#00796B;stroke-width:1.8px;stroke-dasharray:5 4",[672,735,737],{"x":731,"y":736,"style":685},"164","dependency",[672,739,741],{"x":731,"y":740,"style":685},"182","override",[672,743,746],{"x":731,"y":744,"style":745},"204","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","fastest, no I\u002FO code",[704,748],{"x1":749,"y1":732,"x2":749,"y2":697,"style":750},"365","stroke:currentColor;stroke-width:1.6px;stroke-dasharray:5 4",[672,752,753],{"x":749,"y":736,"style":685},"monkeypatch",[672,755,757],{"x":749,"y":756,"style":745},"186","import-path bound",[704,759],{"x1":760,"y1":732,"x2":760,"y2":697,"style":733},"545",[672,762,611],{"x":760,"y":736,"style":685},[672,764,765],{"x":760,"y":756,"style":745},"client code still runs",[767,768,769],"figcaption",{},"Everything to the left of your cut is exercised by the test; everything to the right is assumed.",[771,772,774],"h2",{"id":773},"choose-by-what-you-want-to-test","Choose by What You Want to Test",[590,776,777],{},"The three techniques are not interchangeable, because they cut the call chain at different points and therefore leave different amounts of code under test.",[590,779,780,781,784],{},"A ",[593,782,783],{},"dependency override"," replaces the object your endpoint asked for. Everything from the endpoint down is real; the client is a stand-in. Use it when the endpoint's logic is what you are testing — its branching, its error mapping, its response shape — and the upstream is incidental.",[590,786,787,790,791,794],{},[593,788,789],{},"Monkeypatching"," replaces an attribute on a module. It works anywhere, including on code that never went through the dependency system, which is exactly why it is the fallback rather than the default: it binds to ",[604,792,793],{},"where a name is imported",", not to what it is.",[590,796,797,802,803,806,807,810],{},[593,798,799],{},[609,800,801],{},"httpx.MockTransport"," replaces the transport underneath an ",[609,804,805],{},"httpx"," client. Your real client code runs — URL construction, headers, timeouts, ",[609,808,809],{},"raise_for_status()",", response parsing — and only the socket is faked. Use it when the client code is the code you are testing, or when you want to assert on the exact request that would go out.",[590,812,813,814,816],{},"The rough rule: override when the upstream is a detail, ",[609,815,611],{}," when the upstream call is the point, monkeypatch when neither seam exists.",[771,818,820],{"id":819},"dependency-overrides","Dependency Overrides",[590,822,823,824,826,827,830],{},"FastAPI keeps a dict on the app: ",[609,825,621],{},", mapping the original callable to a replacement. During resolution, ",[609,828,829],{},"solve_dependencies"," looks up each dependency there before calling it. Because the key is the callable object itself, the substitution reaches every declaration site — the path operation, router-level dependencies, and any sub-dependency several levels down.",[832,833,838],"pre",{"className":834,"code":835,"language":836,"meta":837,"style":837},"language-python shiki shiki-themes github-light-high-contrast","from typing import Annotated\n\nimport httpx\nfrom fastapi import Depends, FastAPI, HTTPException\n\n\nclass WeatherClient:\n    \"\"\"The production client. In a test it must never be constructed against the real host.\"\"\"\n\n    def __init__(self, base_url: str) -> None:\n        self.base_url = base_url\n\n    async def forecast(self, city: str) -> dict[str, object]:\n        CALLS.append(f\"REAL call to {self.base_url}\u002Fforecast?city={city}\")\n        raise RuntimeError(\"this would open a socket to the real weather API\")\n\n\nclass FakeWeatherClient:\n    \"\"\"The test double. Same shape, no I\u002FO, and it records what was asked of it.\"\"\"\n\n    def __init__(self) -> None:\n        self.seen: list[str] = []\n\n    async def forecast(self, city: str) -> dict[str, object]:\n        self.seen.append(city)\n        CALLS.append(f\"fake forecast({city})\")\n        if city == \"nowhere\":\n            raise httpx.HTTPStatusError(\"404\", request=None, response=None)\n        return {\"city\": city, \"temp_c\": 21.5, \"source\": \"fake\"}\n\n\ndef get_weather_client() -> WeatherClient:\n    return WeatherClient(\"https:\u002F\u002Fapi.weather.example.com\")\n\n\n@app.get(\"\u002Fweather\u002F{city}\")\nasync def weather(\n    city: str,\n    client: Annotated[WeatherClient, Depends(get_weather_client)],\n) -> dict[str, object]:\n    try:\n        return await client.forecast(city)\n    except httpx.HTTPStatusError:\n        raise HTTPException(status_code=502, detail=f\"weather upstream has no city {city}\")\n\n\nfake = FakeWeatherClient()\napp.dependency_overrides[get_weather_client] = lambda: fake\n","python","",[609,839,840,858,865,873,886,891,896,909,916,921,945,960,965,997,1040,1057,1062,1067,1077,1083,1088,1102,1120,1125,1148,1156,1179,1196,1228,1265,1270,1275,1287,1301,1306,1311,1329,1343,1354,1360,1373,1381,1392,1401,1439,1444,1449,1460],{"__ignoreMap":837},[841,842,844,848,852,855],"span",{"class":704,"line":843},1,[841,845,847],{"class":846},"sTJeM","from",[841,849,851],{"class":850},"sigWx"," typing ",[841,853,854],{"class":846},"import",[841,856,857],{"class":850}," Annotated\n",[841,859,861],{"class":704,"line":860},2,[841,862,864],{"emptyLinePlaceholder":863},true,"\n",[841,866,868,870],{"class":704,"line":867},3,[841,869,854],{"class":846},[841,871,872],{"class":850}," httpx\n",[841,874,876,878,881,883],{"class":704,"line":875},4,[841,877,847],{"class":846},[841,879,880],{"class":850}," fastapi ",[841,882,854],{"class":846},[841,884,885],{"class":850}," Depends, FastAPI, HTTPException\n",[841,887,889],{"class":704,"line":888},5,[841,890,864],{"emptyLinePlaceholder":863},[841,892,894],{"class":704,"line":893},6,[841,895,864],{"emptyLinePlaceholder":863},[841,897,899,902,906],{"class":704,"line":898},7,[841,900,901],{"class":846},"class",[841,903,905],{"class":904},"sV4o_"," WeatherClient",[841,907,908],{"class":850},":\n",[841,910,912],{"class":704,"line":911},8,[841,913,915],{"class":914},"sYEJz","    \"\"\"The production client. In a test it must never be constructed against the real host.\"\"\"\n",[841,917,919],{"class":704,"line":918},9,[841,920,864],{"emptyLinePlaceholder":863},[841,922,924,927,931,934,937,940,943],{"class":704,"line":923},10,[841,925,926],{"class":846},"    def",[841,928,930],{"class":929},"sacAq"," __init__",[841,932,933],{"class":850},"(self, base_url: ",[841,935,936],{"class":929},"str",[841,938,939],{"class":850},") -> ",[841,941,942],{"class":929},"None",[841,944,908],{"class":850},[841,946,948,951,954,957],{"class":704,"line":947},11,[841,949,950],{"class":929},"        self",[841,952,953],{"class":850},".base_url ",[841,955,956],{"class":846},"=",[841,958,959],{"class":850}," base_url\n",[841,961,963],{"class":704,"line":962},12,[841,964,864],{"emptyLinePlaceholder":863},[841,966,968,971,974,978,981,983,986,988,991,994],{"class":704,"line":967},13,[841,969,970],{"class":846},"    async",[841,972,973],{"class":846}," def",[841,975,977],{"class":976},"s3dhs"," forecast",[841,979,980],{"class":850},"(self, city: ",[841,982,936],{"class":929},[841,984,985],{"class":850},") -> dict[",[841,987,936],{"class":929},[841,989,990],{"class":850},", ",[841,992,993],{"class":929},"object",[841,995,996],{"class":850},"]:\n",[841,998,1000,1003,1006,1009,1012,1015,1018,1021,1024,1027,1029,1032,1034,1037],{"class":704,"line":999},14,[841,1001,1002],{"class":929},"        CALLS",[841,1004,1005],{"class":850},".append(",[841,1007,1008],{"class":846},"f",[841,1010,1011],{"class":914},"\"REAL call to ",[841,1013,1014],{"class":846},"{",[841,1016,1017],{"class":929},"self",[841,1019,1020],{"class":850},".base_url",[841,1022,1023],{"class":846},"}",[841,1025,1026],{"class":914},"\u002Fforecast?city=",[841,1028,1014],{"class":846},[841,1030,1031],{"class":850},"city",[841,1033,1023],{"class":846},[841,1035,1036],{"class":914},"\"",[841,1038,1039],{"class":850},")\n",[841,1041,1043,1046,1049,1052,1055],{"class":704,"line":1042},15,[841,1044,1045],{"class":846},"        raise",[841,1047,1048],{"class":929}," RuntimeError",[841,1050,1051],{"class":850},"(",[841,1053,1054],{"class":914},"\"this would open a socket to the real weather API\"",[841,1056,1039],{"class":850},[841,1058,1060],{"class":704,"line":1059},16,[841,1061,864],{"emptyLinePlaceholder":863},[841,1063,1065],{"class":704,"line":1064},17,[841,1066,864],{"emptyLinePlaceholder":863},[841,1068,1070,1072,1075],{"class":704,"line":1069},18,[841,1071,901],{"class":846},[841,1073,1074],{"class":904}," FakeWeatherClient",[841,1076,908],{"class":850},[841,1078,1080],{"class":704,"line":1079},19,[841,1081,1082],{"class":914},"    \"\"\"The test double. Same shape, no I\u002FO, and it records what was asked of it.\"\"\"\n",[841,1084,1086],{"class":704,"line":1085},20,[841,1087,864],{"emptyLinePlaceholder":863},[841,1089,1091,1093,1095,1098,1100],{"class":704,"line":1090},21,[841,1092,926],{"class":846},[841,1094,930],{"class":929},[841,1096,1097],{"class":850},"(self) -> ",[841,1099,942],{"class":929},[841,1101,908],{"class":850},[841,1103,1105,1107,1110,1112,1115,1117],{"class":704,"line":1104},22,[841,1106,950],{"class":929},[841,1108,1109],{"class":850},".seen: list[",[841,1111,936],{"class":929},[841,1113,1114],{"class":850},"] ",[841,1116,956],{"class":846},[841,1118,1119],{"class":850}," []\n",[841,1121,1123],{"class":704,"line":1122},23,[841,1124,864],{"emptyLinePlaceholder":863},[841,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146],{"class":704,"line":1127},24,[841,1129,970],{"class":846},[841,1131,973],{"class":846},[841,1133,977],{"class":976},[841,1135,980],{"class":850},[841,1137,936],{"class":929},[841,1139,985],{"class":850},[841,1141,936],{"class":929},[841,1143,990],{"class":850},[841,1145,993],{"class":929},[841,1147,996],{"class":850},[841,1149,1151,1153],{"class":704,"line":1150},25,[841,1152,950],{"class":929},[841,1154,1155],{"class":850},".seen.append(city)\n",[841,1157,1159,1161,1163,1165,1168,1170,1172,1174,1177],{"class":704,"line":1158},26,[841,1160,1002],{"class":929},[841,1162,1005],{"class":850},[841,1164,1008],{"class":846},[841,1166,1167],{"class":914},"\"fake forecast(",[841,1169,1014],{"class":846},[841,1171,1031],{"class":850},[841,1173,1023],{"class":846},[841,1175,1176],{"class":914},")\"",[841,1178,1039],{"class":850},[841,1180,1182,1185,1188,1191,1194],{"class":704,"line":1181},27,[841,1183,1184],{"class":846},"        if",[841,1186,1187],{"class":850}," city ",[841,1189,1190],{"class":846},"==",[841,1192,1193],{"class":914}," \"nowhere\"",[841,1195,908],{"class":850},[841,1197,1199,1202,1205,1208,1210,1213,1215,1217,1219,1222,1224,1226],{"class":704,"line":1198},28,[841,1200,1201],{"class":846},"            raise",[841,1203,1204],{"class":850}," httpx.HTTPStatusError(",[841,1206,1207],{"class":914},"\"404\"",[841,1209,990],{"class":850},[841,1211,1212],{"class":904},"request",[841,1214,956],{"class":846},[841,1216,942],{"class":929},[841,1218,990],{"class":850},[841,1220,1221],{"class":904},"response",[841,1223,956],{"class":846},[841,1225,942],{"class":929},[841,1227,1039],{"class":850},[841,1229,1231,1234,1237,1240,1243,1246,1249,1252,1254,1257,1259,1262],{"class":704,"line":1230},29,[841,1232,1233],{"class":846},"        return",[841,1235,1236],{"class":850}," {",[841,1238,1239],{"class":914},"\"city\"",[841,1241,1242],{"class":850},": city, ",[841,1244,1245],{"class":914},"\"temp_c\"",[841,1247,1248],{"class":850},": ",[841,1250,1251],{"class":929},"21.5",[841,1253,990],{"class":850},[841,1255,1256],{"class":914},"\"source\"",[841,1258,1248],{"class":850},[841,1260,1261],{"class":914},"\"fake\"",[841,1263,1264],{"class":850},"}\n",[841,1266,1268],{"class":704,"line":1267},30,[841,1269,864],{"emptyLinePlaceholder":863},[841,1271,1273],{"class":704,"line":1272},31,[841,1274,864],{"emptyLinePlaceholder":863},[841,1276,1278,1281,1284],{"class":704,"line":1277},32,[841,1279,1280],{"class":846},"def",[841,1282,1283],{"class":976}," get_weather_client",[841,1285,1286],{"class":850},"() -> WeatherClient:\n",[841,1288,1290,1293,1296,1299],{"class":704,"line":1289},33,[841,1291,1292],{"class":846},"    return",[841,1294,1295],{"class":850}," WeatherClient(",[841,1297,1298],{"class":914},"\"https:\u002F\u002Fapi.weather.example.com\"",[841,1300,1039],{"class":850},[841,1302,1304],{"class":704,"line":1303},34,[841,1305,864],{"emptyLinePlaceholder":863},[841,1307,1309],{"class":704,"line":1308},35,[841,1310,864],{"emptyLinePlaceholder":863},[841,1312,1314,1317,1319,1322,1325,1327],{"class":704,"line":1313},36,[841,1315,1316],{"class":976},"@app.get",[841,1318,1051],{"class":850},[841,1320,1321],{"class":914},"\"\u002Fweather\u002F",[841,1323,1324],{"class":846},"{city}",[841,1326,1036],{"class":914},[841,1328,1039],{"class":850},[841,1330,1332,1335,1337,1340],{"class":704,"line":1331},37,[841,1333,1334],{"class":846},"async",[841,1336,973],{"class":846},[841,1338,1339],{"class":976}," weather",[841,1341,1342],{"class":850},"(\n",[841,1344,1346,1349,1351],{"class":704,"line":1345},38,[841,1347,1348],{"class":850},"    city: ",[841,1350,936],{"class":929},[841,1352,1353],{"class":850},",\n",[841,1355,1357],{"class":704,"line":1356},39,[841,1358,1359],{"class":850},"    client: Annotated[WeatherClient, Depends(get_weather_client)],\n",[841,1361,1363,1365,1367,1369,1371],{"class":704,"line":1362},40,[841,1364,985],{"class":850},[841,1366,936],{"class":929},[841,1368,990],{"class":850},[841,1370,993],{"class":929},[841,1372,996],{"class":850},[841,1374,1376,1379],{"class":704,"line":1375},41,[841,1377,1378],{"class":846},"    try",[841,1380,908],{"class":850},[841,1382,1384,1386,1389],{"class":704,"line":1383},42,[841,1385,1233],{"class":846},[841,1387,1388],{"class":846}," await",[841,1390,1391],{"class":850}," client.forecast(city)\n",[841,1393,1395,1398],{"class":704,"line":1394},43,[841,1396,1397],{"class":846},"    except",[841,1399,1400],{"class":850}," httpx.HTTPStatusError:\n",[841,1402,1404,1406,1409,1412,1414,1417,1419,1422,1424,1426,1429,1431,1433,1435,1437],{"class":704,"line":1403},44,[841,1405,1045],{"class":846},[841,1407,1408],{"class":850}," HTTPException(",[841,1410,1411],{"class":904},"status_code",[841,1413,956],{"class":846},[841,1415,1416],{"class":929},"502",[841,1418,990],{"class":850},[841,1420,1421],{"class":904},"detail",[841,1423,956],{"class":846},[841,1425,1008],{"class":846},[841,1427,1428],{"class":914},"\"weather upstream has no city ",[841,1430,1014],{"class":846},[841,1432,1031],{"class":850},[841,1434,1023],{"class":846},[841,1436,1036],{"class":914},[841,1438,1039],{"class":850},[841,1440,1442],{"class":704,"line":1441},45,[841,1443,864],{"emptyLinePlaceholder":863},[841,1445,1447],{"class":704,"line":1446},46,[841,1448,864],{"emptyLinePlaceholder":863},[841,1450,1452,1455,1457],{"class":704,"line":1451},47,[841,1453,1454],{"class":850},"fake ",[841,1456,956],{"class":846},[841,1458,1459],{"class":850}," FakeWeatherClient()\n",[841,1461,1463,1466,1468,1471],{"class":704,"line":1462},48,[841,1464,1465],{"class":850},"app.dependency_overrides[get_weather_client] ",[841,1467,956],{"class":846},[841,1469,1470],{"class":846}," lambda",[841,1472,1473],{"class":850},": fake\n",[590,1475,1476],{},"The real client raises if it is ever called, which turns \"the override did not apply\" from a silent network call into an immediate failure. That is worth doing in your own fakes: make the production path in a test environment loud.",[590,1478,1479],{},"Real output for a success and an upstream error:",[832,1481,1485],{"className":1482,"code":1484,"language":672,"meta":837},[1483],"language-text","$ GET \u002Fweather\u002Fberlin\n200 OK\n{\n  \"city\": \"berlin\",\n  \"temp_c\": 21.5,\n  \"source\": \"fake\"\n}\n\n$ GET \u002Fweather\u002Fnowhere\n502 Bad Gateway\n{\n  \"detail\": \"weather upstream has no city nowhere\"\n}\n",[609,1486,1484],{"__ignoreMap":837},[590,1488,1489,1490,1493,1494,1497],{},"The second response is the more valuable one. The double raised ",[609,1491,1492],{},"httpx.HTTPStatusError",", and the endpoint's ",[609,1495,1496],{},"except"," branch mapped it to a 502 with a useful detail — error-mapping logic that would otherwise only run in production. A test double that cannot fail cannot test failure handling.",[590,1499,1500,1501,1504],{},"In pytest, the override belongs in a fixture with teardown, because ",[609,1502,1503],{},"dependency_overrides"," lives on the app object and persists between tests:",[832,1506,1508],{"className":834,"code":1507,"language":836,"meta":837,"style":837},"@pytest.fixture\ndef fake_weather(app):\n    fake = FakeWeatherClient()\n    app.dependency_overrides[get_weather_client] = lambda: fake\n    yield fake\n    app.dependency_overrides.pop(get_weather_client, None)   # Restore, do not clear().\n",[609,1509,1510,1515,1525,1534,1545,1553],{"__ignoreMap":837},[841,1511,1512],{"class":704,"line":843},[841,1513,1514],{"class":976},"@pytest.fixture\n",[841,1516,1517,1519,1522],{"class":704,"line":860},[841,1518,1280],{"class":846},[841,1520,1521],{"class":976}," fake_weather",[841,1523,1524],{"class":850},"(app):\n",[841,1526,1527,1530,1532],{"class":704,"line":867},[841,1528,1529],{"class":850},"    fake ",[841,1531,956],{"class":846},[841,1533,1459],{"class":850},[841,1535,1536,1539,1541,1543],{"class":704,"line":875},[841,1537,1538],{"class":850},"    app.dependency_overrides[get_weather_client] ",[841,1540,956],{"class":846},[841,1542,1470],{"class":846},[841,1544,1473],{"class":850},[841,1546,1547,1550],{"class":704,"line":888},[841,1548,1549],{"class":846},"    yield",[841,1551,1552],{"class":850}," fake\n",[841,1554,1555,1558,1560,1563],{"class":704,"line":893},[841,1556,1557],{"class":850},"    app.dependency_overrides.pop(get_weather_client, ",[841,1559,942],{"class":929},[841,1561,1562],{"class":850},")   ",[841,1564,1566],{"class":1565},"sFeEa","# Restore, do not clear().\n",[590,1568,1569,1570,1573,1574,1576,1577,1581,1582,1586],{},"Popping the single key rather than calling ",[609,1571,1572],{},"clear()"," matters once you have more than one fixture installing overrides — ",[609,1575,1572],{}," removes another fixture's override too, and the resulting failure appears in an unrelated test. The override mechanism itself is covered in ",[636,1578,1580],{"href":1579},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002F","overriding dependencies in tests",", and it works best when the client was injected in the first place, which is an argument for the patterns in ",[636,1583,1585],{"href":1584},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002F","best practices for FastAPI dependency injection",".",[771,1588,1590],{"id":1589},"monkeypatching-and-why-it-is-third-choice","Monkeypatching, and Why It Is Third Choice",[590,1592,1593,1596],{},[609,1594,1595],{},"monkeypatch.setattr"," swaps an attribute for the duration of a test and restores it afterwards. The catch is which attribute:",[832,1598,1600],{"className":834,"code":1599,"language":836,"meta":837,"style":837},"# app\u002Fservices\u002Fweather.py\nfrom app.clients import fetch_forecast     # Bound at import time.\n\n\nasync def summarise(city: str) -> str:\n    data = await fetch_forecast(city)\n    return f\"{data['temp_c']}C\"\n",[609,1601,1602,1607,1622,1626,1630,1650,1662],{"__ignoreMap":837},[841,1603,1604],{"class":704,"line":843},[841,1605,1606],{"class":1565},"# app\u002Fservices\u002Fweather.py\n",[841,1608,1609,1611,1614,1616,1619],{"class":704,"line":860},[841,1610,847],{"class":846},[841,1612,1613],{"class":850}," app.clients ",[841,1615,854],{"class":846},[841,1617,1618],{"class":850}," fetch_forecast     ",[841,1620,1621],{"class":1565},"# Bound at import time.\n",[841,1623,1624],{"class":704,"line":867},[841,1625,864],{"emptyLinePlaceholder":863},[841,1627,1628],{"class":704,"line":875},[841,1629,864],{"emptyLinePlaceholder":863},[841,1631,1632,1634,1636,1639,1642,1644,1646,1648],{"class":704,"line":888},[841,1633,1334],{"class":846},[841,1635,973],{"class":846},[841,1637,1638],{"class":976}," summarise",[841,1640,1641],{"class":850},"(city: ",[841,1643,936],{"class":929},[841,1645,939],{"class":850},[841,1647,936],{"class":929},[841,1649,908],{"class":850},[841,1651,1652,1655,1657,1659],{"class":704,"line":893},[841,1653,1654],{"class":850},"    data ",[841,1656,956],{"class":846},[841,1658,1388],{"class":846},[841,1660,1661],{"class":850}," fetch_forecast(city)\n",[841,1663,1664,1666,1669,1671,1673,1676,1679,1682,1684],{"class":704,"line":898},[841,1665,1292],{"class":846},[841,1667,1668],{"class":846}," f",[841,1670,1036],{"class":914},[841,1672,1014],{"class":846},[841,1674,1675],{"class":850},"data[",[841,1677,1678],{"class":914},"'temp_c'",[841,1680,1681],{"class":850},"]",[841,1683,1023],{"class":846},[841,1685,1686],{"class":914},"C\"\n",[590,1688,1689,1690,1693,1694,1697,1698,1701,1702,1705],{},"Patching ",[609,1691,1692],{},"app.clients.fetch_forecast"," here does nothing, because ",[609,1695,1696],{},"app.services.weather"," already holds its own reference to the original function. You must patch ",[609,1699,1700],{},"app.services.weather.fetch_forecast"," — the name in the module that ",[604,1703,1704],{},"uses"," it. That rule (\"patch where it is looked up, not where it is defined\") is well known and still routinely gets it wrong after a refactor moves an import, at which point the patch silently stops applying and the test makes a real network call. In CI with no egress it fails confusingly; with egress it passes, slowly, against production.",[590,1707,1708],{},"Monkeypatching is still the right tool when there is no seam: a module-level client created at import time, a third-party SDK that constructs its own transport, a function called from code that never sees a dependency. When you do use it, patch the narrowest thing you can and assert the patch took effect.",[771,1710,1712],{"id":1711},"httpx-mocktransport","httpx MockTransport",[590,1714,1715,1717,1718,1721,1722,1725],{},[609,1716,611],{}," takes a handler function that receives an ",[609,1719,1720],{},"httpx.Request"," and returns an ",[609,1723,1724],{},"httpx.Response",". The client is otherwise entirely real.",[832,1727,1729],{"className":834,"code":1728,"language":836,"meta":837,"style":837},"def handler(request: httpx.Request) -> httpx.Response:\n    CALLS.append(f\"MockTransport saw {request.method} {request.url}\")\n    if request.url.path == \"\u002Fv1\u002Frates\":\n        return httpx.Response(200, json={\"USD\": 1.0, \"EUR\": 0.92})\n    return httpx.Response(500, json={\"error\": \"unmapped route\"})\n\n\nmock_transport = httpx.MockTransport(handler)\n\n\n@app.get(\"\u002Frates\")\nasync def rates() -> dict[str, object]:\n    # Production builds this client without the transport argument; the test injects it.\n    async with httpx.AsyncClient(\n        transport=mock_transport, base_url=\"https:\u002F\u002Frates.example.com\"\n    ) as client:\n        response = await client.get(\"\u002Fv1\u002Frates\", headers={\"accept\": \"application\u002Fjson\"})\n        response.raise_for_status()\n        return {\"rates\": response.json(), \"upstream_status\": response.status_code}\n",[609,1730,1731,1741,1771,1786,1825,1852,1856,1860,1870,1874,1878,1889,1909,1914,1924,1942,1953,1987,1992],{"__ignoreMap":837},[841,1732,1733,1735,1738],{"class":704,"line":843},[841,1734,1280],{"class":846},[841,1736,1737],{"class":976}," handler",[841,1739,1740],{"class":850},"(request: httpx.Request) -> httpx.Response:\n",[841,1742,1743,1746,1748,1750,1753,1755,1758,1760,1762,1765,1767,1769],{"class":704,"line":860},[841,1744,1745],{"class":929},"    CALLS",[841,1747,1005],{"class":850},[841,1749,1008],{"class":846},[841,1751,1752],{"class":914},"\"MockTransport saw ",[841,1754,1014],{"class":846},[841,1756,1757],{"class":850},"request.method",[841,1759,1023],{"class":846},[841,1761,1236],{"class":846},[841,1763,1764],{"class":850},"request.url",[841,1766,1023],{"class":846},[841,1768,1036],{"class":914},[841,1770,1039],{"class":850},[841,1772,1773,1776,1779,1781,1784],{"class":704,"line":867},[841,1774,1775],{"class":846},"    if",[841,1777,1778],{"class":850}," request.url.path ",[841,1780,1190],{"class":846},[841,1782,1783],{"class":914}," \"\u002Fv1\u002Frates\"",[841,1785,908],{"class":850},[841,1787,1788,1790,1793,1795,1797,1800,1802,1804,1807,1809,1812,1814,1817,1819,1822],{"class":704,"line":875},[841,1789,1233],{"class":846},[841,1791,1792],{"class":850}," httpx.Response(",[841,1794,680],{"class":929},[841,1796,990],{"class":850},[841,1798,1799],{"class":904},"json",[841,1801,956],{"class":846},[841,1803,1014],{"class":850},[841,1805,1806],{"class":914},"\"USD\"",[841,1808,1248],{"class":850},[841,1810,1811],{"class":929},"1.0",[841,1813,990],{"class":850},[841,1815,1816],{"class":914},"\"EUR\"",[841,1818,1248],{"class":850},[841,1820,1821],{"class":929},"0.92",[841,1823,1824],{"class":850},"})\n",[841,1826,1827,1829,1831,1834,1836,1838,1840,1842,1845,1847,1850],{"class":704,"line":888},[841,1828,1292],{"class":846},[841,1830,1792],{"class":850},[841,1832,1833],{"class":929},"500",[841,1835,990],{"class":850},[841,1837,1799],{"class":904},[841,1839,956],{"class":846},[841,1841,1014],{"class":850},[841,1843,1844],{"class":914},"\"error\"",[841,1846,1248],{"class":850},[841,1848,1849],{"class":914},"\"unmapped route\"",[841,1851,1824],{"class":850},[841,1853,1854],{"class":704,"line":893},[841,1855,864],{"emptyLinePlaceholder":863},[841,1857,1858],{"class":704,"line":898},[841,1859,864],{"emptyLinePlaceholder":863},[841,1861,1862,1865,1867],{"class":704,"line":911},[841,1863,1864],{"class":850},"mock_transport ",[841,1866,956],{"class":846},[841,1868,1869],{"class":850}," httpx.MockTransport(handler)\n",[841,1871,1872],{"class":704,"line":918},[841,1873,864],{"emptyLinePlaceholder":863},[841,1875,1876],{"class":704,"line":923},[841,1877,864],{"emptyLinePlaceholder":863},[841,1879,1880,1882,1884,1887],{"class":704,"line":947},[841,1881,1316],{"class":976},[841,1883,1051],{"class":850},[841,1885,1886],{"class":914},"\"\u002Frates\"",[841,1888,1039],{"class":850},[841,1890,1891,1893,1895,1898,1901,1903,1905,1907],{"class":704,"line":962},[841,1892,1334],{"class":846},[841,1894,973],{"class":846},[841,1896,1897],{"class":976}," rates",[841,1899,1900],{"class":850},"() -> dict[",[841,1902,936],{"class":929},[841,1904,990],{"class":850},[841,1906,993],{"class":929},[841,1908,996],{"class":850},[841,1910,1911],{"class":704,"line":967},[841,1912,1913],{"class":1565},"    # Production builds this client without the transport argument; the test injects it.\n",[841,1915,1916,1918,1921],{"class":704,"line":999},[841,1917,970],{"class":846},[841,1919,1920],{"class":846}," with",[841,1922,1923],{"class":850}," httpx.AsyncClient(\n",[841,1925,1926,1929,1931,1934,1937,1939],{"class":704,"line":1042},[841,1927,1928],{"class":904},"        transport",[841,1930,956],{"class":846},[841,1932,1933],{"class":850},"mock_transport, ",[841,1935,1936],{"class":904},"base_url",[841,1938,956],{"class":846},[841,1940,1941],{"class":914},"\"https:\u002F\u002Frates.example.com\"\n",[841,1943,1944,1947,1950],{"class":704,"line":1059},[841,1945,1946],{"class":850},"    ) ",[841,1948,1949],{"class":846},"as",[841,1951,1952],{"class":850}," client:\n",[841,1954,1955,1958,1960,1962,1965,1968,1970,1973,1975,1977,1980,1982,1985],{"class":704,"line":1064},[841,1956,1957],{"class":850},"        response ",[841,1959,956],{"class":846},[841,1961,1388],{"class":846},[841,1963,1964],{"class":850}," client.get(",[841,1966,1967],{"class":914},"\"\u002Fv1\u002Frates\"",[841,1969,990],{"class":850},[841,1971,1972],{"class":904},"headers",[841,1974,956],{"class":846},[841,1976,1014],{"class":850},[841,1978,1979],{"class":914},"\"accept\"",[841,1981,1248],{"class":850},[841,1983,1984],{"class":914},"\"application\u002Fjson\"",[841,1986,1824],{"class":850},[841,1988,1989],{"class":704,"line":1069},[841,1990,1991],{"class":850},"        response.raise_for_status()\n",[841,1993,1994,1996,1998,2001,2004,2007],{"class":704,"line":1079},[841,1995,1233],{"class":846},[841,1997,1236],{"class":850},[841,1999,2000],{"class":914},"\"rates\"",[841,2002,2003],{"class":850},": response.json(), ",[841,2005,2006],{"class":914},"\"upstream_status\"",[841,2008,2009],{"class":850},": response.status_code}\n",[590,2011,2012],{},"Real output for a stubbed route and an un-stubbed one:",[832,2014,2017],{"className":2015,"code":2016,"language":672,"meta":837},[1483],"$ GET \u002Frates\n200 OK\n{\n  \"rates\": {\n    \"USD\": 1.0,\n    \"EUR\": 0.92\n  },\n  \"upstream_status\": 200\n}\n\n$ GET \u002Funmapped\n200 OK\n{\n  \"status\": 500,\n  \"body\": {\n    \"error\": \"unmapped route\"\n  }\n}\n",[609,2018,2016],{"__ignoreMap":837},[590,2020,2021],{},"And the record of what the transport actually saw, which is what you assert on when the request itself is the contract:",[832,2023,2026],{"className":2024,"code":2025,"language":672,"meta":837},[1483],"$ GET \u002Fcalls\n200 OK\n{\n  \"calls\": [\n    \"fake forecast(berlin)\",\n    \"fake forecast(nowhere)\",\n    \"MockTransport saw GET https:\u002F\u002Frates.example.com\u002Fv1\u002Frates\",\n    \"MockTransport saw GET https:\u002F\u002Frates.example.com\u002Fv1\u002Funknown\"\n  ],\n  \"cities_seen_by_fake\": [\n    \"berlin\",\n    \"nowhere\"\n  ]\n}\n",[609,2027,2025],{"__ignoreMap":837},[590,2029,2030,2031,2033,2034,2037],{},"Notice the fully-resolved URLs. ",[609,2032,1936],{}," joining, path handling and query encoding all ran for real, so a bug in URL construction — the kind a hand-written fake would paper over — is visible here. The handler's fall-through returning a 500 rather than raising is a deliberate choice: an un-stubbed route answers loudly instead of hanging or escaping to the network. Making the fall-through ",[609,2035,2036],{},"raise AssertionError(f\"unexpected request: {request.url}\")"," is even stricter and often better in a real suite.",[590,2039,2040,2041,2043],{},"The injection point is the only awkward part. Production code should not know about ",[609,2042,611],{},", so the client needs to come from somewhere replaceable — which brings you back to a dependency:",[832,2045,2047],{"className":834,"code":2046,"language":836,"meta":837,"style":837},"def get_http_client() -> httpx.AsyncClient:\n    return httpx.AsyncClient(base_url=settings.rates_url, timeout=5.0)\n\n\n# In the test:\napp.dependency_overrides[get_http_client] = lambda: httpx.AsyncClient(\n    transport=httpx.MockTransport(handler), base_url=\"https:\u002F\u002Frates.example.com\"\n)\n",[609,2048,2049,2059,2083,2087,2091,2096,2108,2124],{"__ignoreMap":837},[841,2050,2051,2053,2056],{"class":704,"line":843},[841,2052,1280],{"class":846},[841,2054,2055],{"class":976}," get_http_client",[841,2057,2058],{"class":850},"() -> httpx.AsyncClient:\n",[841,2060,2061,2063,2066,2068,2070,2073,2076,2078,2081],{"class":704,"line":860},[841,2062,1292],{"class":846},[841,2064,2065],{"class":850}," httpx.AsyncClient(",[841,2067,1936],{"class":904},[841,2069,956],{"class":846},[841,2071,2072],{"class":850},"settings.rates_url, ",[841,2074,2075],{"class":904},"timeout",[841,2077,956],{"class":846},[841,2079,2080],{"class":929},"5.0",[841,2082,1039],{"class":850},[841,2084,2085],{"class":704,"line":867},[841,2086,864],{"emptyLinePlaceholder":863},[841,2088,2089],{"class":704,"line":875},[841,2090,864],{"emptyLinePlaceholder":863},[841,2092,2093],{"class":704,"line":888},[841,2094,2095],{"class":1565},"# In the test:\n",[841,2097,2098,2101,2103,2105],{"class":704,"line":893},[841,2099,2100],{"class":850},"app.dependency_overrides[get_http_client] ",[841,2102,956],{"class":846},[841,2104,1470],{"class":846},[841,2106,2107],{"class":850},": httpx.AsyncClient(\n",[841,2109,2110,2113,2115,2118,2120,2122],{"class":704,"line":898},[841,2111,2112],{"class":904},"    transport",[841,2114,956],{"class":846},[841,2116,2117],{"class":850},"httpx.MockTransport(handler), ",[841,2119,1936],{"class":904},[841,2121,956],{"class":846},[841,2123,1941],{"class":914},[841,2125,2126],{"class":704,"line":911},[841,2127,1039],{"class":850},[590,2129,2130],{},"That combination — an override supplying a real client with a mock transport — is the strongest of the three. The endpoint is real, the client is real, and only the network is fake.",[771,2132,2134],{"id":2133},"verification","Verification",[590,2136,2137,2138,2141],{},"The assertions that catch mocking mistakes are about the ",[604,2139,2140],{},"call",", not just the response:",[832,2143,2145],{"className":834,"code":2144,"language":836,"meta":837,"style":837},"def test_endpoint_calls_upstream_once(client, fake_weather):\n    client.get(\"\u002Fweather\u002Fberlin\")\n    assert fake_weather.seen == [\"berlin\"]      # Called once, with the right argument.\n\n\ndef test_no_real_network_calls(client, fake_weather):\n    client.get(\"\u002Fweather\u002Fberlin\")\n    assert not any(call.startswith(\"REAL\") for call in CALLS)\n",[609,2146,2147,2157,2167,2189,2193,2197,2206,2214],{"__ignoreMap":837},[841,2148,2149,2151,2154],{"class":704,"line":843},[841,2150,1280],{"class":846},[841,2152,2153],{"class":976}," test_endpoint_calls_upstream_once",[841,2155,2156],{"class":850},"(client, fake_weather):\n",[841,2158,2159,2162,2165],{"class":704,"line":860},[841,2160,2161],{"class":850},"    client.get(",[841,2163,2164],{"class":914},"\"\u002Fweather\u002Fberlin\"",[841,2166,1039],{"class":850},[841,2168,2169,2172,2175,2177,2180,2183,2186],{"class":704,"line":867},[841,2170,2171],{"class":846},"    assert",[841,2173,2174],{"class":850}," fake_weather.seen ",[841,2176,1190],{"class":846},[841,2178,2179],{"class":850}," [",[841,2181,2182],{"class":914},"\"berlin\"",[841,2184,2185],{"class":850},"]      ",[841,2187,2188],{"class":1565},"# Called once, with the right argument.\n",[841,2190,2191],{"class":704,"line":875},[841,2192,864],{"emptyLinePlaceholder":863},[841,2194,2195],{"class":704,"line":888},[841,2196,864],{"emptyLinePlaceholder":863},[841,2198,2199,2201,2204],{"class":704,"line":893},[841,2200,1280],{"class":846},[841,2202,2203],{"class":976}," test_no_real_network_calls",[841,2205,2156],{"class":850},[841,2207,2208,2210,2212],{"class":704,"line":898},[841,2209,2161],{"class":850},[841,2211,2164],{"class":914},[841,2213,1039],{"class":850},[841,2215,2216,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245],{"class":704,"line":911},[841,2217,2171],{"class":846},[841,2219,2220],{"class":846}," not",[841,2222,2223],{"class":929}," any",[841,2225,2226],{"class":850},"(call.startswith(",[841,2228,2229],{"class":914},"\"REAL\"",[841,2231,2232],{"class":850},") ",[841,2234,2235],{"class":846},"for",[841,2237,2238],{"class":850}," call ",[841,2240,2241],{"class":846},"in",[841,2243,2244],{"class":929}," CALLS",[841,2246,1039],{"class":850},[590,2248,2249],{},"The second test is the guard rail. In a large suite it is worth enforcing globally with a session fixture that patches the socket module to raise, so any test that escapes to the network fails immediately with a clear message rather than being slow and flaky.",[590,2251,2252],{},"Keep at least one test that talks to the real service, run on a schedule rather than in every pipeline. Every mock encodes an assumption about the upstream's behaviour on the day you wrote it, and the failure mode of a stale mock is a green suite and a broken production integration.",[771,2254,2256],{"id":2255},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2258,2259,2260,2262],{},"Every mock is a claim about someone else's system. A hand-written double drifts from the real API as it evolves, and the drift is invisible until deployment. ",[609,2261,611],{}," reduces the surface of that claim to the response payload; a dependency override widens it to the whole client interface.",[590,2264,2265],{},"Over-mocking is the more common failure than under-mocking. A test that replaces the database, the cache, the HTTP client and the clock verifies that your mocks are wired together correctly. Prefer real in-memory substitutes where they exist — SQLite for a repository test, a real Redis in a container for cache behaviour — and reserve doubles for the boundaries you genuinely cannot cross in CI.",[590,2267,2268,2269,2272,2273,2276,2277,2280],{},"Finally, remember that overriding a dependency bypasses everything that dependency would have done, including validation and authentication. Overriding ",[609,2270,2271],{},"get_current_user"," for convenience means your endpoints are never tested with a real token path, and an authorisation bug there is exactly the kind of thing tests are supposed to catch. Override the ",[604,2274,2275],{},"client",", not the ",[604,2278,2279],{},"guard",", wherever you can.",[771,2282,2284],{"id":2283},"faq","FAQ",[590,2286,2287,2290],{},[593,2288,2289],{},"Why are dependency overrides usually better than monkeypatching in FastAPI?","\nBecause the override is keyed by the dependency callable rather than by an import path. FastAPI substitutes it everywhere that dependency is declared, including in nested dependencies, so a refactor that moves a module cannot silently stop the mock from applying.",[590,2292,2293,2296],{},[593,2294,2295],{},"When is httpx MockTransport the right tool?","\nWhen the code under test is the HTTP client code itself: headers, retries, timeouts, status handling and response parsing. MockTransport intercepts at the transport layer, so all of that still runs and only the socket is replaced.",[590,2298,2299,2302],{},[593,2300,2301],{},"Do I need to reset dependency_overrides between tests?","\nYes. The dictionary lives on the app object, so an override installed by one test leaks into every test that follows. Clear it in fixture teardown, and prefer restoring the previous value over calling clear() if other fixtures also install overrides.",[590,2304,2305,2308],{},[593,2306,2307],{},"Should the test double match the real client's interface exactly?","\nClose enough that the endpoint cannot tell them apart, including the exceptions it raises. A double that only implements the happy path lets error handling ship untested, which is usually the code most in need of a test.",[590,2310,2311,2314],{},[593,2312,2313],{},"Can I still get real behaviour with mocks in place?","\nOnly for the layer you did not replace. Keep at least one contract test that runs against the real service or a recorded contract, because every mocking strategy encodes your assumption about the upstream, and assumptions drift.",[771,2316,2318],{"id":2317},"related-reading","Related Reading",[597,2320,2321,2329,2337,2346,2355],{},[600,2322,2323,2326,2327,1586],{},[593,2324,2325],{},"Up to the topic:"," ",[636,2328,363],{"href":638},[600,2330,2331,2326,2334,1586],{},[593,2332,2333],{},"The override mechanism:",[636,2335,2336],{"href":1579},"Overriding dependencies in tests",[600,2338,2339,2326,2342,1586],{},[593,2340,2341],{},"Async test wiring:",[636,2343,2345],{"href":2344},"\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002F","Testing async endpoints with pytest-asyncio",[600,2347,2348,2326,2351,1586],{},[593,2349,2350],{},"Choosing a client:",[636,2352,2354],{"href":2353},"\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002F","TestClient vs httpx AsyncClient",[600,2356,2357,2326,2360,1586],{},[593,2358,2359],{},"Injectable design:",[636,2361,2362],{"href":1584},"Best practices for FastAPI dependency injection",[2364,2365,2366],"style",{},"html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}",{"title":837,"searchDepth":860,"depth":860,"links":2368},[2369,2370,2371,2372,2373,2374,2375,2376],{"id":773,"depth":860,"text":774},{"id":819,"depth":860,"text":820},{"id":1589,"depth":860,"text":1590},{"id":1711,"depth":860,"text":1712},{"id":2133,"depth":860,"text":2134},{"id":2255,"depth":860,"text":2256},{"id":2283,"depth":860,"text":2284},{"id":2317,"depth":860,"text":2318},"2026-07-20","Compare dependency overrides, monkeypatching and httpx MockTransport for outbound calls in FastAPI tests, with real responses from each replaced boundary.","md",[2381,2383,2385,2387,2389],{"q":2289,"a":2382},"Because the override is keyed by the dependency callable rather than by an import path. FastAPI substitutes it everywhere that dependency is declared, including in nested dependencies, so a refactor that moves a module cannot silently stop the mock from applying.",{"q":2295,"a":2384},"When the code under test is the HTTP client code itself: headers, retries, timeouts, status handling and response parsing. MockTransport intercepts at the transport layer, so all of that still runs and only the socket is replaced.",{"q":2301,"a":2386},"Yes. The dictionary lives on the app object, so an override installed by one test leaks into every test that follows. Clear it in fixture teardown, and prefer restoring the previous value over calling clear() if other fixtures also install overrides.",{"q":2307,"a":2388},"Close enough that the endpoint cannot tell them apart, including the exceptions it raises. A double that only implements the happy path lets error handling ship untested, which is usually the code most in need of a test.",{"q":2313,"a":2390},"Only for the layer you did not replace. Keep at least one contract test that runs against the real service or a recorded contract, because every mocking strategy encodes your assumption about the upstream, and assumptions drift.",null,{"slug":2393,"breadcrumb":2394},"mocking-external-services-in-tests",[2395,2398,2401,2402],{"label":2396,"path":2397},"Home","\u002F",{"label":2399,"path":2400},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":363,"path":638},{"label":2403,"path":2404},"Mocking External Services in Tests","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002F",{"title":369,"description":2378},"article","aIXUq8xjWPl-QvarNByXIZN4rgPu0rBdahTUk3EbbV4",[2391,2391],1784588203038]