简体   繁体   English

如何在 FastAPI 测试中添加依赖覆盖

[英]How to add depedency overriding in FastAPI testing

I'm new to FastAPI, I have implemented everything but when it comes to testing the API I can't override a dependency.我是 FastAPI 的新手,我已经实现了所有内容,但是在测试 API 时我无法覆盖依赖项。

Here is my code:这是我的代码:

test_controller.py测试控制器.py

import pytest
from starlette.testclient import TestClient
from app.main import app
from app.core.manager_imp import ManagerImp


@pytest.fixture()
def client():
    with TestClient(app) as test_client:
        yield test_client


async def over_create_record():
    return {"msg": "inserted successfully"}

app.dependency_overrides[ManagerImp.create_record] = over_create_record

def test_post(client):
    data = {"name": "John", "email": "john@abc.com"}
    response = client.post("/person/", json=data)
    assert response.status_code == 200
    assert response.json() == {"msg": "inserted successfully"}

controller.py controller.py

from app.controllers.v1.controller import Controller
from fastapi import status, HTTPException
from app.models.taxslip import Person
from app.core.manager_imp import ManagerImp
from app.core.duplicate_exception import DuplicateException
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter

router = InferringRouter(tags=["Person"]) 
@cbv(router)
class ControllerImp(Controller):
    manager = ManagerImp()

    @router.post("/person/")
    async def create_record(self, person: Person):
        """
        Person: A person object
        returns response if the person was inserted into the database
        """
        try:
            response = await self.manager.create_record(person.dict())
            return response
        except DuplicateException as e:
            return e

manager_imp.py manager_imp.py

from fastapi import HTTPException, status
from app.database.database_imp import DatabaseImp
from app.core.manager import Manager
from app.core.duplicate_exception import DuplicateException


class ManagerImp(Manager):
    database = DatabaseImp()
    async def create_record(self, taxslip: dict):
        try:
            response = await self.database.add(taxslip)
            return response
        except DuplicateException:
            raise HTTPException(409, "Duplicate data")

In testing I want to override create_record function from ManagerImp class so that I could get this response {"msg": "inserted successfully"} .在测试中,我想覆盖 ManagerImp class 中的 create_record function 以便我可以获得此响应{"msg": "inserted successfully"} Basically, I want to mock ManagerImp create_record function. I have tried as you can see in test_controller.py but I still get the original response.基本上,我想模拟 ManagerImp create_record function。正如您在test_controller.py中看到的那样,我已经尝试过,但我仍然得到原始响应。

You're not using the dependency injection system to get the ManagerImp.create_record function, so there is nothing to override.您没有使用依赖项注入系统来获取ManagerImp.create_record function,因此无需覆盖任何内容。

Since you're not using FastAPI's Depends to get your dependency - FastAPI has no way of returning the alternative function.由于您没有使用 FastAPI 的Depends来获取依赖项 - FastAPI 无法返回替代项 function。

In your case you'll need to use a regular mocking library instead, such as unittest.mock or pytest-mock .在您的情况下,您需要使用常规的 mocking 库,例如 unittest.mock 或pytest-mock

I'd also like to point out that initializing a shared dependency as in you've done here by default will share the same instance across all instances of ControllerImp instead of being re-created for each instance of ControllerImp .我还想指出,默认情况下初始化共享依赖项将跨所有ControllerImp实例共享同一个实例,而不是为每个ControllerImp实例重新创建。

The cbv decorator changes things a bit , and as mentioned in the documentation: cbv 装饰器稍微改变了一些东西,如文档中所述:

For each shared dependency, add a class attribute with a value of type Depends对于每个共享依赖项,添加一个值为Depends类型的 class 属性

So to get this to match the FastAPI way of doing things and make the cbv decorator work as you want to:因此,要使其与 FastAPI 的做事方式相匹配,并使cbv装饰器按照您的意愿工作:

def get_manager():
    return ManagerImp()

@cbv(router)
class ControllerImp(Controller):
    manager = Depends(get_manager)

And when you do it this way, you can use dependency_overrides as you planned:当你这样做时,你可以按照你的计划使用dependency_overrides

app.dependency_overrides[get_manager] = lambda: return MyFakeManager()

If you only want to replace the create_record function, you'll still have to use regular mocking.如果您只想替换create_record function,您仍然必须使用常规 mocking。

You'll also have to remove the dependency override after the test has finished unless you want it to apply to all tests, so use yield inside your fixture and then remove the override when the fixture starts executing again.您还必须在测试完成后删除依赖覆盖,除非您希望它应用于所有测试,因此请在您的夹具中使用yield ,然后在夹具再次开始执行时删除覆盖。

I think you should put your app.dependency_overrides inside the function with @pytest.fixture .我认为您应该将app.dependency_overrides放在带有@pytest.fixture的 function 中。 Try to put it inside your client() .尝试将其放入您的client()中。

@pytest.fixture()
def client():
    app.dependency_overrides[ManagerImp.create_record] = over_create_record
    with TestClient(app) as test_client:
        yield test_client

because every test will run the fresh app , meaning it will reset everything from one to another test and only related things bind with the pytest will effect the test.因为每个测试都会运行新的app ,这意味着它将重置从一个测试到另一个测试的所有内容,并且只有与pytest绑定的相关内容才会影响测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM