7

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"}
Josh
  • 1,556
  • 1
  • 10
  • 21
Shod
  • 801
  • 3
  • 12
  • 33

2 Answers2

3

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"}
Shod
  • 801
  • 3
  • 12
  • 33
1

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"}
    
SoloSami
  • 13
  • 4