Home > front end >  How can I test for Exception cases in FastAPI with Pytest?
How can I test for Exception cases in FastAPI with Pytest?

Time:08-12

I am struggling to write test cases that will trigger an Exception within one of my FastAPI routes. I was thinking pytest.Raises would do what I intend, however that by itself doesn't seem to be doing what I thought it would.

Since the TestClient runs the API client pretty much separately, it makes sense that I would have this issue - that being said, I am not sure what the best practice is to ensure a high code coverage in testing.

Here is my test function:

def test_function_exception():
  with pytest.raises(Exception):
    response = client.post("/")
    assert response.status_code == 400

and here is the barebones route that I am hitting:

@router.post("/")
def my_function():
  try:
    do_something()
  except Exception as e:
    raise HTTPException(400, "failed to do something")

Is there anyway that I can catch this Exception without making changes to the API route? If changes are needed, what are the changes required to ensure thorough testing?

CodePudding user response:

Following the discussion below the question, I assembled a working example for you. Typically, if you can't logically hit your except block, you can ensure that the try block is raising an Exception by monkey patching the function that is tried, and replace it with something that definitely will raise an exception. In the below example, I will change the function do_something() that is defined in app.py with replace_do_something() that will just raise an Exception when called.

You can put the following files in the same folder (not a module) and try it for yourself:

File app.py:

from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
import pytest

app = FastAPI()


def do_something():
    return "world"


@app.get("/myroute")
async def myroute():
    try:
        text = do_something()
        return {"hello": text}
    except Exception:
        raise HTTPException(400, "something went wrong")

File test_app.py:

import pytest
from fastapi.testclient import TestClient

from app import app

client = TestClient(app)


def replace_do_something():
    raise Exception()
    return


def test_read_main(monkeypatch: pytest.MonkeyPatch):
    response = client.get("/myroute")
    assert response.status_code == 200
    assert response.json() == {"hello": "world"}


def test_read_main_with_error(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setattr("app.do_something", replace_do_something)
    # Here we replace any reference to do_something 
    # with replace_do_something. Note the 'app.' prefix!
    
    response = client.get("/myroute")
    assert response.status_code == 400
    assert response.json() == {"detail": "something went wrong"}

You can call the test_app.py file with pytest (I also have pytest-cov installed to demonstrate the 100% coverage):

(venv) jarro@MacBook-Pro-van-Jarro fastapi-github-issues % pytest --cov=app SO/pytestwithmock/test_app.py
===================================================== test session starts =====================================================
platform darwin -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/jarro/Development/fastapi-github-issues
plugins: anyio-3.6.1, cov-3.0.0
collected 2 items                                                                                                             

SO/pytestwithmock/test_app.py ..                                                                                        [100%]

---------- coverage: platform darwin, python 3.10.5-final-0 ----------
Name                       Stmts   Miss  Cover
----------------------------------------------
SO/pytestwithmock/app.py      13      0   100%
----------------------------------------------
TOTAL                         13      0   100%


====================================================== 2 passed in 0.19s ======================================================
  • Related