DEV Community

Python FastAPI: Tutorial to Test HTTP Client Requests

Testing HTTP Client Requests

Disclaimer: I’m learning Python, so please correct me if I’ve written something wrong!

In this tutorial, we’ll explore how to test HTTP client calls in a FastAPI Python application using two different methods:

  • unittest.mock: This is a built-in Python library used for mocking objects in tests. It allows you to replace parts of your system under test and make assertions about how they have been used.
  • httpretty: This is a third-party library that allows you to mock HTTP requests at a low level by creating a fake HTTP server.

To run the tests, just execute this on the root of the project directory:

pytest
Enter fullscreen mode Exit fullscreen mode

You should see output similar to this:

$ pytest
============================================================================================== test session starts ==============================================================================================
platform linux -- Python 3.11.6, pytest-8.2.0, pluggy-1.5.0
rootdir: /home/wsl/github/python-study/api-client
plugins: anyio-4.2.0
collected 3 items

tests/test_main.py .                                                                                                                                                                                      [ 33%]
tests/test_main_httppretty_mock.py .                                                                                                                                                                      [ 66%]
tests/test_main_magic_mock.py .                                                                                                                                                                           [100%]

=============================================================================================== 3 passed in 0.39s ===============================================================================================
Enter fullscreen mode Exit fullscreen mode

This output indicates that all three tests passed successfully. The percentage in brackets shows the progress of the test run.

Versions of my modules

$ pip list
Package                            Version
---------------------------------- -----------
fastapi                            0.111.0
httpretty                          1.1.4
pydantic                           2.5.3
pytest                             8.2.0
requests                           2.31.0
Enter fullscreen mode Exit fullscreen mode

Project structure

.
├── app
│   ├── __init__.py
│   └── main.py
└── tests
    ├── __init__.py
    ├── test_main_httppretty_mock.py
    └── test_main_magic_mock.py
Enter fullscreen mode Exit fullscreen mode

__init__.py are just empty files

main.py

from pydantic import BaseModel
from fastapi import FastAPI, HTTPException
from typing import Union
import requests
import os
import logging

API_SERVER_URL = os.getenv("API_SERVER_URL", default="http://localhost:8010")
API_LOG_LEVEL = os.getenv("API_LOG_LEVEL", default="INFO")

# Configure basic logging
logging.basicConfig(level=API_LOG_LEVEL)
logger = logging.getLogger()

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float
    is_offer: Union[bool, None] = None


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}", response_model=Item)
def read_item(item_id: int, q: Union[str, None] = None):
    try:
        response = requests.get(
            f"{API_SERVER_URL}/items/{item_id}", params={"q": q})
        # Raises HTTPError for bad responses (4XX or 5XX)
        response.raise_for_status()
        item_data = response.json()
        item = Item(**item_data)  # Deserialize the JSON into an Item object
        return item
    except requests.RequestException as e:
        logger.error(f"Request failed: {str(e)}")  # Log the error
        raise HTTPException(status_code=500, detail=str(e))

Enter fullscreen mode Exit fullscreen mode

test_main_magic_mock.py

from fastapi.testclient import TestClient
import pytest
from unittest.mock import patch, MagicMock
from app.main import app

client = TestClient(app)


@pytest.fixture
def mock_requests_get():
    # Create a MagicMock object to mock requests.get
    with patch('requests.get') as mock_get:
        # Prepare a mock response object with necessary methods
        mock_response = MagicMock()
        mock_response.json.return_value = {
            "name": "Sample Item",
            "price": 100.0,
            "is_offer": None
        }
        mock_response.raise_for_status = MagicMock()
        mock_get.return_value = mock_response
        yield mock_get


# The mock_requests_get fixture is automatically used here.
def test_read_item(mock_requests_get):
    response = client.get("/items/1")
    assert response.status_code == 200
    assert response.json() == {
        "name": "Sample Item",
        "price": 100.0,
        "is_offer": None
    }
    # Assert if requests.get was called correctly
    mock_requests_get.assert_called_once_with(
        "http://localhost:8010/items/1",
        params={'q': None}
    )
Enter fullscreen mode Exit fullscreen mode

test_main_httppretty_mock.py

import httpretty
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)


@httpretty.activate
def test_read_item():
    # Mocking the external API call
    httpretty.register_uri(
        httpretty.GET,
        "http://localhost:8010/items/1",
        body='{"name": "Sample Item", "price": 100.0, "is_offer": null}',
        content_type="application/json",
        status=200  # Define the expected status code
    )

    # Call the endpoint
    response = client.get("/items/1")

    # Assertions
    assert response.status_code == 200
    assert response.json() == {
        "name": "Sample Item",
        "price": 100.0,
        "is_offer": None
    }
Enter fullscreen mode Exit fullscreen mode

Top comments (0)