如何正确创建Mock方法测试FastAPI POST接口,避免数据库写入
解决FastAPI接口单元测试中Mock DAO类方法失效的问题
问题描述
为FastAPI的POST接口编写单元测试时,期望通过Mock AnimalDAO.create_obj 避免数据库写入,但测试中Mock未生效:接口仍会向数据库写入数据,且断言create_obj被调用一次失败(实际调用0次)。
相关代码
测试代码
@pytest.mark.asyncio async def test_animals_create(async_animals_client: AsyncClient, mocker): path = 'create' animal_data = { 'nick_name': 'ASASDASD', 'status': 1, 'weight': 1.02, 'sex': 1, } mock_create_obj = mocker.patch.object( AnimalDAO, 'create_obj', return_value=None, new_callable=mocker.AsyncMock, ) response = await async_animals_client.post( url=f'{async_animals_client.base_url}{path}', json=animal_data, ) mock_create_obj.assert_called_once_with(fields=animal_data) assert response.status_code == status.HTTP_201_CREATED
接口代码
@router.post('/create', status_code=status.HTTP_201_CREATED) async def create_animal(fields: AnimalsScheme): await AnimalDAO.create_obj(fields=fields.model_dump())
AnimalDAO代码
class AnimalDAO(BaseDAO): """DAO для модели Animals.""" obj = Animals
BaseDAO代码
class BaseDAO: obj = None @classmethod async def create_obj(cls, fields: dict) -> None: async with async_session() as session: new_obj = cls.obj( **fields, ) session.add(new_obj) await session.commit()
错误信息
FAILED app/animal/tests/unit/test_create_animals.py::test_animals_create - AssertionError: Expected 'create_obj' to be called once. Called 0 times.
问题原因
AnimalDAO 并未重写父类 BaseDAO 的 create_obj 类方法,调用 AnimalDAO.create_obj 时实际执行的是 BaseDAO.create_obj。直接patch AnimalDAO.create_obj 无法替换父类中的方法实现,导致Mock未生效。
解决方案
方案一:patch父类BaseDAO的create_obj方法
修改测试中的patch逻辑,直接patch父类的类方法,这样所有继承BaseDAO的子类调用该方法时都会触发Mock:
from unittest.mock import ANY from app.path.to.base_dao import BaseDAO # 替换为实际的BaseDAO导入路径 @pytest.mark.asyncio async def test_animals_create(async_animals_client: AsyncClient, mocker): path = 'create' animal_data = { 'nick_name': 'ASASDASD', 'status': 1, 'weight': 1.02, 'sex': 1, } # patch BaseDAO的create_obj方法 mock_create_obj = mocker.patch.object( BaseDAO, 'create_obj', return_value=None, new_callable=mocker.AsyncMock, ) response = await async_animals_client.post( url=f'{async_animals_client.base_url}{path}', json=animal_data, ) # 类方法会自动传入cls参数,用ANY匹配任意cls值 mock_create_obj.assert_called_once_with(ANY, fields=animal_data) assert response.status_code == status.HTTP_201_CREATED
方案二:在AnimalDAO中显式重写create_obj方法
如果希望直接patch AnimalDAO 的方法,可以在AnimalDAO中显式重写create_obj,使其调用父类方法,之后再patch AnimalDAO.create_obj:
修改AnimalDAO代码
class AnimalDAO(BaseDAO): """DAO для модели Animals.""" obj = Animals @classmethod async def create_obj(cls, fields: dict) -> None: await super().create_obj(fields)
修改测试代码
@pytest.mark.asyncio async def test_animals_create(async_animals_client: AsyncClient, mocker): path = 'create' animal_data = { 'nick_name': 'ASASDASD', 'status': 1, 'weight': 1.02, 'sex': 1, } mock_create_obj = mocker.patch.object( AnimalDAO, 'create_obj', return_value=None, new_callable=mocker.AsyncMock, ) response = await async_animals_client.post( url=f'{async_animals_client.base_url}{path}', json=animal_data, ) # 此时断言无需考虑cls参数,因为重写后的方法直接接收fields mock_create_obj.assert_called_once_with(fields=animal_data) assert response.status_code == status.HTTP_201_CREATED
额外注意事项
- 确保测试中导入的
AnimalDAO/BaseDAO与接口代码中使用的是同一个模块的类,避免因导入路径不一致导致类对象不匹配。 - 检查
AnimalsScheme.model_dump()的输出是否与测试中的animal_data完全一致,若存在默认字段(如id),需在测试数据中补充或使用ANY匹配参数。
内容的提问来源于stack exchange,提问作者Apple
相关产品推荐
相关产品推荐

