httpx: Test client usage question: how to trigger lifespan startup and shutdown ?

I have an issue switching from the TestClient from async_asgi_testclient to the httpx.AsyncClient : the startup and shutdown events of my app aren’t triggered by the latter.

It’s probably a bad usage on my end, but if anyone could pinpoint what I’m doing wrong it would be of tremendous help,

I wrote a small set of 2 tests below to better describe the situation, the one with httpx.AsyncClient fails while the other passes fine:

import logging

import pytest
from async_asgi_testclient import TestClient
from httpx import AsyncClient
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

app = Starlette()


@app.on_event("startup")
async def startup():
    logger.debug("starting the app")


@app.on_event("shutdown")
async def shutdown():
    logger.debug("shutdown the app")


@app.route("/")
def homepage(request: Request):
    logger.debug("HOME")
    return JSONResponse({"hello": "world"})


@pytest.fixture
async def asyncasgitestclient():
    yield TestClient(app)


@pytest.fixture
async def httpxclient():
    yield AsyncClient(app=app, base_url="https://domain.tld")


@pytest.mark.asyncio
async def test_homepage_with_httpxclient(httpxclient, caplog):
    async with httpxclient:
        url = app.url_path_for("homepage")
        resp = await httpxclient.get(url)
        assert resp.status_code == 200
        assert resp.json() == {"hello": "world"}
    logger.debug(caplog.messages)
    assert all(
        x in caplog.messages for x in ["starting the app", "HOME", "shutdown the app"]
    )


@pytest.mark.asyncio
async def test_homepage_with_async_asgi_testclient(asyncasgitestclient, caplog):
    async with asyncasgitestclient:
        url = app.url_path_for("homepage")
        resp = await asyncasgitestclient.get(url)
        assert resp.status_code == 200
        assert resp.json() == {"hello": "world"}
    logger.debug(caplog.messages)
    assert all(
        x in caplog.messages for x in ["starting the app", "HOME", "shutdown the app"]
    )

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 19 (16 by maintainers)

Most upvoted comments

Okay so, I created https://github.com/florimondmanca/asgi-lifespan to experiment on this. It’s mostly empty for now but I’ll add LifespanContext based on the implementation in #352, and I plan to have LifespanMiddleware be mostly ripped out from Starlette.

Any thoughts on the proposed API there?

Dropping in just to say that it’s a bummer that a full ASGI use-case can’t be solved with just HTTPX. IMO this is one of our defining features as an HTTP client in the ecosystem.

@Ammar-Azman Hey! This issue is for adding support for calling lifespan events from a HTTPX client not defining them. Please open a discussion on FastAPI instead. Make sure you read the documentation on lifespan events first — I believe they are always application not router scoped.

@kernc Thanks, I logged #1441 so that we’re properly tracking this, and so that’s visible to anyone up for contributing. 😃

Yup, I’m sold on the idea of decoupling the two, definitely.

Actually, thinking about this in terms of allowing users to switch to AsyncClient from Starlette’s TestClient (with the end goal that TestClient could be removed from Starlette entirely), the AsyncClient not implementing the lifespan protocol is definitely a bug.

I think there’s a case to be made there, but I’m quite keen on treating it as an entierly seperate issue. (Users are also free to deal with application setup/teardown in test cases in more explicit ways rather than relying on ASGI’s lifespan messaging)