question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`RequestValidationError` is raised when testing, instead of returing `422` response

See original GitHub issue

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn’t find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google “How to X in FastAPI” and didn’t find any information.
  • I already read and followed all the tutorial in the docs and didn’t find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

import uuid

from fastapi import FastAPI, APIRouter, Header, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel


# Models and CRUD
class Item(BaseModel):
    id: int
    content: str


def crud_get_items():
    # This function lies in a separate module
    return [
        Item(id=1, content="First item"),
        Item(id=2, content="Second item"),
    ]


# App
app = APIRouter()


@app.get("/")
async def get_items(user_id: str = Header()):
    data = crud_get_items()
    if not len(data):  # This is redundant in the example, but exist in "normal" codebase
        raise HTTPException(status_code=404, detail="No items found")

    return data


# Tests
client = TestClient(app)


def test_get_items():
    headers = {"user-id": str(uuid.uuid4())}
    res = client.get("/", headers=headers)
    assert res.status_code == 200


def test_get_items_without_userid():
    res = client.get("/")
    assert res.status_code == 422

Description

When testing my app I noticed, that sending request to router without header parameter set, I get RequestValidationError instead of 422 status code. This doesn’t happen, when using FastAPI instead of APIRouter.

One could say, that an easy fix to this is to wrap the second test code in pytest.raises(), but this will only execute the client.get while skipping all asserts the test has.

from fastapi.exceptions import RequestValidationError

def test_get_items_without_userid():
    with pytest.raises(RequestValidationError):
        res = client.get("/")          # This line will execute
        assert res.status_code == 422  # This line won't (and any below) 

Operating System

Windows

Operating System Details

Windows 10, 21H2

FastAPI Version

0.85.0

Python Version

Python 3.10.4

Additional Context

No response

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

4reactions
JarroVGITcommented, Sep 18, 2022

The TestClient is created to test Starlette applications, and by extension you can use it to test FastAPI applications. It is not designed to test Starlette’s Router or FastAPI’s APIRouter directly. As you have experienced, it can yield unexpected results. Most notably: The difference between sending a request to a FastAPI application and sending a request to the APIRouter directly (using it as an ASGI application on its own), is that no middleware is executed when doing the latter.

And by default (and design), there are always at least 2 middleware present in your FastAPI application (which will not be present in your APIRouter instance: one that catches and handles exceptions like HTTPException and one (the very last one) that handles any uncaught exception and returns a HTTP 500 Internal Server Error to ensure your server keeps running.

So, when using APIRouter directly in your TestClient, any raised HTTPExceptions will not result in a proper response, rather than actually raised (and unhandled) exception.

So, to answer your question:

But at this point I want to know if APIRouter is untestable on it’s own by design?

The behaviour that you are experiencing is by design, and when testing an APIRouter one should keep in mind that FastAPI + APIRouter has different behaviour than just the APIRouter as an ASGI application on its own.

0reactions
tiangolocommented, Nov 20, 2022

Thanks for the help here @JarroVGIT ! 👏 You rock! 🙇

Thanks for reporting back and closing the issue @jkazimierczak 👍

Read more comments on GitHub >

github_iconTop Results From Across the Web

[Bug] 422 Response Not Being Used For Validation ... - GitHub
I have unit tests that validate an endpoint, so I am using Prism like ... it to return the server response instead of...
Read more >
Handling Errors - FastAPI
These handlers are in charge of returning the default JSON responses when you raise an HTTPException and when the request has invalid data....
Read more >
FastAPI - Pydantic - Value Error Raises Internal Server Error
RequestValidationError is a sub-class of Pydantic's ValidationError . The result of this is that a standard Validation error raised during ...
Read more >
3 Ways to Handle Errors in FastAPI That You Need to Know
Notice that the status in the response changed based on what we specified. 2. Create a Custom Exception #. If you are feeling...
Read more >
Complex Request Validation in FastAPI with Pydantic
... day (Saturday or Sunday), return a request-validation error response. ... and day_number > 365: return JSONResponse( status_code=422, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found