12

I have a FastAPI app with a route prefix as /api/v1.

When I run the test it throws 404. I see this is because the TestClient is not able to find the route at /ping, and works perfectly when the route in the test case is changed to /api/v1/ping.

Is there a way in which I can avoid changing all the routes in all the test functions as per the prefix? This seems to be cumbersome as there are many test cases, and also because I dont want to have a hard-coded dependency of the route prefix in my test cases. Is there a way in which I can configure the prefix in the TestClient just as we did in app, and simply mention the route just as mentioned in the routes.py?

routes.py

from fastapi import APIRouter

router = APIRouter()

@router.get("/ping")
async def ping_check():
    return {"msg": "pong"}

main.py

from fastapi import FastAPI
from routes import router

app = FastAPI()
app.include_router(prefix="/api/v1")

In the test file I have:

test.py

from main import app
from fastapi.testclient import TestClient

client = TestClient(app)

def test_ping():
    response = client.get("/ping")
    assert response.status_code == 200
    assert response.json() == {"msg": "pong"}

3 Answers 3

5

Figured out a workaround for this.

The TestClient has an option to accept a base_url, which is then urljoined with the route path. So I appended the route prefix to this base_url.

source:

url = urljoin(self.base_url, url)

However, there is a catch to this - urljoin concatenates as expected only when the base_url ends with a / and the path does not start with a /. This SO answer explains it well.

This resulted in the below change:

test.py

from main import app, ROUTE_PREFIX
from fastapi.testclient import TestClient

client = TestClient(app)
client.base_url += ROUTE_PREFIX  # adding prefix
client.base_url = client.base_url.rstrip("/") + "/"  # making sure we have 1 and only 1 `/`

def test_ping():
    response = client.get("ping")  # notice the path no more begins with a `/`
    assert response.status_code == 200
    assert response.json() == {"msg": "pong"}
Sign up to request clarification or add additional context in comments.

Comments

3

Had to cast @Shod's answer to str for it to work on FastAPI 0.104

client = TestClient(app)
client.base_url = str(client.base_url) + settings.api_prefix  # adding prefix
client.base_url = str(client.base_url).rstrip("/") + "/"  # making sure we have 1 and only 1 `/`

Comments

0

The above work-around (by Shod) worked for me, but I had to pass the APIRouter object instead of FastAPI object to the testclient. I was receiving a 404 error otherwise. Below is a sample code for how it worked for me.

from fastapi import FastAPI, APIRouter
from fastapi.testclient import TestClient

app = FastAPI()
router = APIRouter(prefix="/sample")

app.include_router(router)

@router.post("/s1")
def read_main():
    return {"msg": "Hello World"}

client = TestClient(router)
client.base_url += "/sample"
client.base_url = client.base_url.rstrip("/") + "/"

def test_main():
    response = client.post("s1")
    assert response.status_code == 200
    assert response.json() == {"msg": "Hello World"}
    

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.