I have the following FastAPI module in python with the corresponding test, but am never able to get an HTTP 200 response from the await redis_service.set_key call I am making. Any suggestions on what I could be doing wrong?
THIS IS THE EXISTING CODE (I CAN REFACTOR THIS, BUT NEED WORKING TESTS BEFORE ATTEMPTING REFACTOR
from fastapi import FastAPI, HTTPException, Depends, Request
from .logs.log_messages import LOG_MESSAGES_INFO
from services.cache.service.cache_service import RedisService
# Define the service name for logging
service_name = "cache-service"
# Initialize the logger
logger = ServiceLogger(
service_name, LOG_MESSAGES_INFO, os.getenv("LOG_FILE_PATH", "api.log")
)
# Create the FastAPI app
app = FastAPI(docs_url="/api", root_path="/plt/cache-service")
redis_url = os.getenv("REDIS_URL", "redis://127.0.0.1:6379")
redis_service = RedisService(redis_url)
# Define the endpoints
@app.post("/redis/set-key/")
async def set_key(request: Request, key_value: KeyValue, namespace: str):
try:
cr_id = request.headers.get("correlationid")
logger.log("info", "MSREDI001", id=cr_id, method="set_key")
# A BREAKPOINT HERE, DOESN'T ALLOW ME TO STEP INTO set_key
# AND DIRECTLY RAISES AN EXCEPTION WITH 500 = <Response [500 Internal Server Error]>
await redis_service.set_key(namespace, key_value.key, key_value.value, key_value.expire)
return {"status": "success"}
except Exception as e:
logger.log("error", "MSREDE004", method="set_key", id=cr_id, error=str(e))
raise HTTPException(status_code=500, detail=str(e))
The associated test:
import pytest
from unittest.mock import patch, MagicMock
from fastapi.testclient import TestClient
from pydantic import BaseModel
from services.cache.main import app
from fakeredis import aioredis
client = TestClient(app)
@pytest.fixture
def fake_redis():
fake_redis_instance = aioredis.FakeRedis()
return fake_redis_instance
@patch('services.cache.main.redis_service') # Mock the RedisService
@patch('services.cache.main.logger') # Mock the logger
def test_set_key_success(mock_logger, mock_redis_service, fake_redis):
# Mock model for the key-value pair
class KeyValue(BaseModel):
key: str
value: str
expire: int
mock_redis_service.set_key = MagicMock(side_effect=fake_redis.set)
mock_logger.log = MagicMock() # Mock the logger
key_value = KeyValue(key="test_key", value="test_value", expire=3600)
headers = {"correlationid": "4af44d6c-9e52-4929-9ff7-bbae3414e92f"}
response = client.post("/redis/set-key/", json={
"key": key_value.key,
"value": key_value.value,
"expire": key_value.expire
}, headers=headers, params={"namespace": "test_namespace"})
# Assert
assert response.status_code == 200 # ALWAYS FAILS THIS ASSERT
assert response.json() == {"status": "success"}
The direct use of aioredis.FakeRedis: aioredis.FakeRedis
doesn’t properly integrate with the mock
setup for redis_service
.
The redis_service.set_key
is an asynchronous function, and mocking it synchronously with MagicMock can cause issues.
In your case, you can redefine KeyValue
, which might not match the schema used in the application.
Updated Test:
import pytest
from unittest.mock import AsyncMock, patch
from fastapi.testclient import TestClient
from services.cache.main import app, redis_service
from services.cache.main import KeyValue
client = TestClient(app)
@pytest.fixture
def fake_redis():
return AsyncMock()
@patch('services.cache.main.redis_service')
@patch('services.cache.main.logger')
def test_set_key_success(mock_logger, mock_redis_service, fake_redis):
mock_redis_service.set_key = fake_redis.set_key
fake_redis.set_key.return_value = None
key_value = {"key": "test_key", "value": "test_value", "expire": 3600}
headers = {"correlationid": "4af44d6c-9e52-4929-9ff7-bbae3414e92f"}
params = {"namespace": "test_namespace"}
response = client.post("/redis/set-key/", json=key_value, headers=headers, params=params)
assert response.status_code == 200
assert response.json() == {"status": "success"}
mock_logger.log.assert_called_with("info", "MSREDI001", id=headers["correlationid"], method="set_key")
fake_redis.set_key.assert_called_with("test_namespace", "test_key", "test_value", 3600)
@patch('services.cache.main.redis_service')
@patch('services.cache.main.logger')
def test_set_key_failure(mock_logger, mock_redis_service, fake_redis):
mock_redis_service.set_key = fake_redis.set_key
fake_redis.set_key.side_effect = Exception("Test Exception")
key_value = {"key": "test_key", "value": "test_value", "expire": 3600}
headers = {"correlationid": "4af44d6c-9e52-4929-9ff7-bbae3414e92f"}
params = {"namespace": "test_namespace"}
response = client.post("/redis/set-key/", json=key_value, headers=headers, params=params)
assert response.status_code == 500
assert response.json() == {"detail": "Test Exception"}
mock_logger.log.assert_called_with(
"error", "MSREDE004", method="set_key", id=headers["correlationid"], error="Test Exception"
)
fake_redis.set_key.assert_called_with("test_namespace", "test_key", "test_value", 3600)