I've got a simple FastAPI application and I'm trying to create tests with pytest
for it.
My goal is to test how app behaves in case of different errors.
I've got a simple healthcheck route in my app:
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
async def health():
return "It's working ✨"
Now in my pytest module I'm trying to patch above function so that it raises different errors.
I'm using unittest.mock
but I'm getting very strange behavior.
import pytest
from unittest import mock
from fastapi import HTTPException
from starlette.testclient import TestClient
import app.api.health
from app.main import app # this is my application (FastAPI instance) with the `router` attached
@pytest.fixture()
def client():
with TestClient(app) as test_client:
yield test_client
def test_simple(client):
def mock_health_function():
raise HTTPException(status_code=400, detail='gibberish')
with mock.patch('app.api.health.health', mock_health_function):
response = client.get(HEALTHCHECK_PATH)
with pytest.raises(HTTPException): # this check passes successfully - my exception is raised
app.api.health.health()
assert response.status_code != 200 # this check does not pass. The original function was called as if nothing was patched
Despite the fact that the exact same function is called inside the test, API test client still calls the original function when I hit the endpoint.
Why does mock.patch
not work properly when function is not called directly in the test?
Or maybe I should approach my problem in some different way?
You can use monkeypatch
fixture to patch your function.
First pull out the code section you want to patch:
from fastapi import FastAPI
app = FastAPI()
def response():
return "It's working ✨"
@app.get("/health")
async def health():
return response()
Then use monkeypatch in your test
import pytest
from fastapi import HTTPException
from starlette.testclient import TestClient
from app import main
def mocked_response():
raise HTTPException(status_code=400, detail='gibberish')
@pytest.fixture()
def client():
from app.main import app
with TestClient(app) as test_client:
yield test_client
def test_simple(client, monkeypatch):
monkeypatch.setattr(main, "response", mocked_response)
resp = client.get("/health")
assert resp.status_code == 400
assert resp.json()["detail"] == "gibberish"
Another approach would be to use Dependencies, together with dependencies_overrides. This probably won't work for all scenarios but for your given use case it does.
from fastapi import FastAPI, Depends
app = FastAPI()
def response():
return "It's working ✨"
@app.get("/health")
async def health(resp=Depends(response)):
return resp
In your test client you can now override the dependency like this:
import pytest
from fastapi import HTTPException
from starlette.testclient import TestClient
from app.main import response
def mocked_response():
raise HTTPException(status_code=400, detail='gibberish')
@pytest.fixture()
def client():
from app.main import app
app.dependency_overrides[response] = mocked_response
with TestClient(app) as test_client:
yield test_client
def test_simple(client):
resp = client.get("/health")
assert resp.status_code == 400
assert resp.json()["detail"] == "gibberish"
If you need to add arguments to your response function you could make use of the closure pattern
def response_closure():
def response(arg):
return arg
return response
@app.get("/health")
async def health(resp=Depends(response_closure)):
return resp("It's working ✨")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With