简体   繁体   English

如何为向另一个端点发出请求的 FastAPI 端点创建单元测试?

[英]How to create unit tests for a FastAPI endpoint that makes request to another endpoint?

I have a FastAPI app that makes requests to other endpoint within a function that handles a particular request.我有一个 FastAPI 应用程序,它在处理特定请求的函数中向其他端点发出请求。

How can I build unit tests for this endpoint using fastapi.testclient.TestClient ?如何使用fastapi.testclient.TestClient为此端点构建单元测试?

import fastapi
import requests
import os

app = fastapi.FastAPI()

# in production this will be a k8s service to allow for 
# automatic scaling and load balancing.
# The default value works when the app is run using uvicorn
service_host = os.environ.get('SERVICE_HOST', 'localhost:8000')

@app.post('/spawner')
def spawn():
    # an endpoint that makes multiple requests to set of worker endpoints
    for i in range(4):
         url = 'http://'+service_host+f'/create/{i}'
         requests.post(url)
    return {'msg': 'spawned the creation/update of 4 resources'}

@app.post('/create/{index}')
def worker(index: int):
    # an endpoint that does the actual work of creating/updating the resource
    return {'msg': f'Created/updated resource {index}'}

How to write the unit tests depends on what specifically you want to be checking.如何编写单元测试取决于您具体要检查的内容。

For example, from your sample code, if you just want to check that your /spawner endpoint properly calls your /create endpoint a certain number of times, you can use Python's unittest.mock.patch to patch the requests.post call such that it doesn't make the actual call, but you can inspect the call it would have made.例如,从您的示例代码中,如果您只想检查您的/spawner端点是否正确调用了您的/create端点一定次数,您可以使用 Python 的unittest.mock.patch来修补requests.post调用,以便它不会进行实际调用,但您可以检查它本应进行的调用。 (See How can I mock requests and the response? for examples on how to mock external calls made by the requests library.) (请参阅如何模拟请求和响应?以获取有关如何模拟requests库进行的外部调用的示例。)

You still use FastAPI's TestClient to make the call to your endpoint, but while the .post call is patched:您仍然使用FastAPI 的TestClient来调用您的端点,但是在修补.post调用时:

from fastapi.testclient import TestClient
from main import app

from unittest.mock import patch

def test_spawn():
    client = TestClient(app)
    mocked_response = requests.Response()
    mocked_response.status_code = 200

    with patch("requests.post", return_value=mocked_response) as mocked_request:
        response = client.post("/spawner")

    # Expect that the requests.post was called 4 times
    # and it called the /create/{index} endpoint
    assert mocked_request.call_count == 4
    called_urls = [call.args[0] for call in mocked_request.call_args_list]
    assert called_urls[0].endswith("/create/0")
    assert called_urls[1].endswith("/create/1")
    assert called_urls[2].endswith("/create/2")
    assert called_urls[3].endswith("/create/3")

    # Expect to get the msg
    assert response.status_code == 200
    assert response.json() == {"msg": "spawned the creation/update of 4 resources"}

patch returns a Mock object, which has attributes like call_count telling you how many times the requests.post function was called and a call_args_list storing the arguments for each call to requests.post . patch返回一个Mock对象,它具有诸如call_count类的属性,告诉您requests.post函数被调用了多少次,以及一个call_args_list存储每次调用requests.post的参数。 Then, your test can also assert that if everything behaved as expected, then it should return the expected response.然后,您的测试还可以断言,如果一切都按预期运行,那么它应该返回预期的响应。

Now, going back to what I said, the usefulness of this test largely depends on what specifically are you hoping to gain from writing the tests.现在,回到我所说的,这个测试的有用性在很大程度上取决于您希望从编写测试中具体获得什么。 One problem with this test is that it isn't strictly black-box testing, because the test "knows" how the endpoint is supposed to work internally.这个测试的一个问题是它不是严格的黑盒测试,因为测试“知道”端点在内部应该如何工作。 If the point is to make sure, for example, that calling /spawner with some query parameter ?count=N results in N number of spawned resources, then maybe this kind of test is useful.例如,如果重点是确保使用某些查询参数调用/spawner ?count=N会产生N个生成的资源,那么这种测试可能很有用。

Another thing, this kind of test makes sense if you are making actual external calls to some other API , and not to your own endpoint.另一件事,如果您正在对其他 API而不是对您自己的端点进行实际的外部调用,则这种测试是有意义的。 User JarroVGit mentioned in the comments that a better implementation would be, instead of making a POST request, you can refactor /spawner to call the endpoint function directly and pass the /create response: 用户 JarroVGit 在评论中提到更好的实现方式是,您可以重构/spawner以直接调用端点函数传递/create响应,而不是发出 POST 请求:

@app.post("/spawner")
def spawn():
    # an endpoint that makes multiple requests to set of worker endpoints
    results = [worker(i) for i in range(4)]
    return {
        "msg": "spawned the creation/update of 4 resources",
        "results": results,
    }

@app.post("/create/{index}")
def worker(index: int):
    # an endpoint that does the actual work of creating/updating the resource
    worker_inst.create(index)
    return {"msg": f"Created/updated resource {index}"}

The test can be made simpler by patching whatever is in /create to not create the actual resources (here, I'm assuming some sort of a worker_inst.create(index) that does the actual creation):通过修补/create中的任何内容以创建实际资源,可以使测试变得更简单(在这里,我假设某种worker_inst.create(index)进行实际创建):

def test_spawn():
    client = TestClient(app)

    # Patch the actual create/update resources to *not* create anything
    with patch.object(WorkerInst, "create"):
        response = client.post("/spawner")

    # Expect to get the msg
    assert response.status_code == 200
    assert response.json() == {
        "msg": "spawned the creation/update of 4 resources",
        "results": [
            {"msg": "Created/updated resource 0"},
            {"msg": "Created/updated resource 1"},
            {"msg": "Created/updated resource 2"},
            {"msg": "Created/updated resource 3"},
        ],
    }

I don't know what kind of "resources" your app is creating, but you can possibly convert that unit test into a more useful integration test, by removing the patch -ing of worker and just letting the worker create the resources, then let the test assert that 1) the correct resources were created, and 2) the /spawner endpoint returned the expected response.我不知道你的应用程序正在创建什么样的“资源”,但你可以将该单元测试转换为更有用的集成测试,方法是删除workerpatch -ing 并让 worker 创建资源,然后让测试断言 1) 创建了正确的资源,并且 2) /spawner端点返回了预期的响应。 Again, depends on what "resources" is and the purpose of your tests.同样,取决于“资源”是什么以及测试的目的。

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

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