litestar-org / litestar

Production-ready, Light, Flexible and Extensible ASGI API framework | Effortlessly Build Performant APIs
https://litestar.dev/
MIT License
5.43k stars 371 forks source link

Documentation: `TestClient.root_path` is not documented #1206

Open LonelyVikingMichael opened 1 year ago

LonelyVikingMichael commented 1 year ago

Describe the bug The TestClient.root_path attribute doesn't seem to apply the given prefix when making test requests.

To Reproduce Define a TestClient fixture with an arbitrary root_path argument and run tests.

Additional context I defined a client fixture as follows, since the project's top level Router has "/api" as a prefix:

@pytest.fixture()
def client(app: "Starlite") -> "Iterator[TestClient]":
    with TestClient(app=app, root_path="/api") as client:
        yield client

The prefix was not applied when running tests, however, after manually prefixing a test path with "/api", the resultant call was made to http://testserver.local/api/api/.... (double prefix)

Fund with Polar

provinzkraut commented 1 year ago

Could you attach an MCVE to this?

provinzkraut commented 1 year ago

So, this is not actually a bug. It's mostly a documentation error on our / HTTPX' side. The root_path is not a "base prefix". It's the equivalent of the ASGI root_path. Quoting this section of the HTTPX docs:

Mount the ASGI application at a subpath by setting root_path.

JacobCoffee commented 10 months ago

Does this exist in 2.x? @litestar-org/members

jeffcarrico commented 6 months ago

I think it is still happening, ran the MCVE below with 2.8.2.

The documentation is

root_path: Path prefix for requests.

If the documentation on root_path is wrong and it is not intended to be used for this, it would be nice to be able to make a test client with a path prefix for requests. Maybe there is another way to do this that I'm missing?

Adapted from https://docs.litestar.dev/2/usage/testing.html#id9

from collections.abc import Iterator

import pytest

from litestar import Litestar, get, Router
from litestar.status_codes import HTTP_200_OK
from litestar.testing import TestClient

@get(path="/health", sync_to_thread=False)
def health_check() -> bool:
    return True

router = Router(path="/api", route_handlers=[health_check])

app = Litestar(route_handlers=[router])

@pytest.fixture(scope="function")
def test_client() -> Iterator[TestClient[Litestar]]:
    with TestClient(app=app) as client:
        yield client

def test_health_check_fixture(test_client: TestClient[Litestar]) -> None:
    assert test_client.get("/api/health").status_code == HTTP_200_OK

@pytest.fixture(scope="function")
def test_client_with_root_path() -> Iterator[TestClient[Litestar]]:
    with TestClient(app=app, root_path="/api") as client:
        yield client

def test_health_check_with_root_path_fixture_1(
    test_client_with_root_path: TestClient[Litestar],
) -> None:
    assert test_client_with_root_path.get("/health").status_code == HTTP_200_OK

def test_health_check_with_root_path_fixture_2(
    test_client_with_root_path: TestClient[Litestar],
) -> None:
    assert test_client_with_root_path.get("/api/health").status_code == HTTP_200_OK
Alc-Alc commented 6 months ago

Maybe there is another way to do this that I'm missing?

You can do the following, I am not sure if you prefer having to type out 'http://testserver.local/api'. I also changed /health to health in test_client_with_root_path.get

@pytest.fixture(scope="function")
def test_client_with_root_path() -> Iterator[TestClient[Litestar]]:
    with TestClient(app=app, base_url='http://testserver.local/api') as client:
        yield client

def test_health_check_with_root_path_fixture_1(
    test_client_with_root_path: TestClient[Litestar],
) -> None:
    assert test_client_with_root_path.get("health").status_code == HTTP_200_OK