blog / · testing
Contract Testing with HTTPX - Part 2
On the previous article, I used RESPX to call the service B from service A. Although it looks cool, we can actually achieve the same goal without using anything besides FastAPI itself.
The Services
Let’s assume that we have similar two services as presented in the previous article.
The difference here is that we’ll be creating a dependency called service_b_client, which
is going to return a httpx.AsyncClient instance that calls our service B.
Service A
service_a.py
from typing import AsyncIterator
import httpx
from fastapi import APIRouter, Depends, FastAPI
router = APIRouter(prefix="/a")
async def service_b_client() -> AsyncIterator[httpx.AsyncClient]:
async with httpx.AsyncClient(base_url="http://localhost:8000/b/") as client:
yield client
@router.get("/")
def get_a():
return
@router.get("/call_b")
async def call_b(client: httpx.AsyncClient = Depends(service_b_client)):
response = await client.get("/")
return response.json()
app = FastAPI()
app.include_router(router)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, port=8001)
- The port is 8001, not 8000, to avoid conflicts with the other service.
Service B
service_b.py
from fastapi import APIRouter, FastAPI
router = APIRouter(prefix="/b")
@router.get("/")
def get_b():
return
app = FastAPI()
app.include_router(router)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, port=8000)
Run
Install the dependencies:
python -m pip install uvicorn fastapi httpx
Then open the terminal and run:
python service_a.py
Then open another terminal, and run:
python service_b.py
Now, let’s call the /a/call_b endpoint.
http :8001/a/call_b
- The HTTP client used is called HTTPie, but you can use
curl, or just go to the browser, and accesshttp://localhost:8001/a/call_b.
The response should look like:
Testing
Since the only difference between this article, and the previous one is the creation of the dependency on service A, you might be guessing that we are going to override the dependency, and… You are right! (if you didn’t, is fine as well 😅)
We are going to use app.dependency_overrides to override the service_b_client dependency, and instead of calling the real service B,
we’ll call the application itself, avoiding the network calls that would potentially slow down our test suite.
Test
test.py
from typing import AsyncIterator
import httpx
import pytest
from service_a import app, service_b_client
from service_b import app as app_b
async def service_b_client_override() -> AsyncIterator[httpx.AsyncClient]:
async with httpx.AsyncClient(app=app_b, base_url="http://test/b") as client:
yield client
@pytest.fixture(name="client")
async def testclient():
app.dependency_overrides[service_b_client] = service_b_client_override
async with httpx.AsyncClient(app=app, base_url="http://test") as client:
yield client
@pytest.mark.anyio
async def test_call_b(client: httpx.AsyncClient) -> None:
response = await client.get("/a/call_b")
assert response.status_code == 200
assert response.json() ==
See more on the Starlette documentation.
Run
Install the dependencies:
python -m pip install pytest httpx trio
Then open the terminal and run:
python -m pytest test.py
Good! Now are able to avoid a lot of network calls, and speed up our test suite. 🎉
Was this useful? Consider sponsoring my open source work. ❤️