簡體   English   中英

如何為向另一個端點發出請求的 FastAPI 端點創建單元測試?

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

我有一個 FastAPI 應用程序,它在處理特定請求的函數中向其他端點發出請求。

如何使用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}'}

如何編寫單元測試取決於您具體要檢查的內容。

例如,從您的示例代碼中,如果您只想檢查您的/spawner端點是否正確調用了您的/create端點一定次數,您可以使用 Python 的unittest.mock.patch來修補requests.post調用,以便它不會進行實際調用,但您可以檢查它本應進行的調用。 (請參閱如何模擬請求和響應?以獲取有關如何模擬requests庫進行的外部調用的示例。)

您仍然使用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返回一個Mock對象,它具有諸如call_count類的屬性,告訴您requests.post函數被調用了多少次,以及一個call_args_list存儲每次調用requests.post的參數。 然后,您的測試還可以斷言,如果一切都按預期運行,那么它應該返回預期的響應。

現在,回到我所說的,這個測試的有用性在很大程度上取決於您希望從編寫測試中具體獲得什么。 這個測試的一個問題是它不是嚴格的黑盒測試,因為測試“知道”端點在內部應該如何工作。 例如,如果重點是確保使用某些查詢參數調用/spawner ?count=N會產生N個生成的資源,那么這種測試可能很有用。

另一件事,如果您正在對其他 API而不是對您自己的端點進行實際的外部調用,則這種測試是有意義的。 用戶 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}"}

通過修補/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"},
        ],
    }

我不知道你的應用程序正在創建什么樣的“資源”,但你可以將該單元測試轉換為更有用的集成測試,方法是刪除workerpatch -ing 並讓 worker 創建資源,然后讓測試斷言 1) 創建了正確的資源,並且 2) /spawner端點返回了預期的響應。 同樣,取決於“資源”是什么以及測試的目的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM