
在开发基于fastapi的实时应用(如在线游戏)时,websocket通信是核心。测试这些应用的健壮性,包括在特定条件下连接应立即关闭的场景,至关重要。例如,当客户端尝试连接到一个不存在的房间或未授权的资源时,服务器应立即终止连接。
使用Pytest对FastAPI的WebSocket端点进行测试时,一个常见的误区是直接将pytest.raises(WebSocketDisconnect)应用于client.websocket_connect上下文管理器。原始尝试如下:
import pytest
from fastapi.testclient import TestClient
from fastapi.websockets import WebSocketDisconnect
# ... (省略导入和应用初始化代码)
class TestWebsocketConnection:
def test_connect_to_non_existing_room_initial_attempt(self):
with pytest.raises(WebSocketDisconnect) as e_info:
with client.websocket_connect("/ws/non_existing_room") as ws:
ws.send_json({"message": "Hello world"})然而,这种测试方法通常会失败,并报告FAILED: DID NOT RAISE <class 'starlette.websockets.WebSocketDisconnect'>。这是因为client.websocket_connect本身主要负责完成WebSocket握手过程。即使服务器在握手完成后立即关闭连接,websocket_connect上下文管理器本身通常不会立即抛出WebSocketDisconnect。WebSocketDisconnect异常通常在尝试对一个已经关闭的连接进行读写操作时才会被触发。
为了更好地理解测试方法,我们回顾一下FastAPI WebSocket端点的基本结构:
from fastapi import APIRouter, WebSocket, Depends
from fastapi.websockets import WebSocketDisconnect
from typing import Annotated
# 假设 GameManager 和 get_manager 已定义
from src.game_manager import GameManager, get_manager
router = APIRouter()
@router.websocket("/ws/{room_name}")
@router.websocket("/ws/{room_name}/{password}")
async def websocket_endpoint(
websocket: WebSocket,
manager: Annotated[GameManager, Depends(get_manager)],
):
room_name = websocket.path_params["room_name"]
password = websocket.path_params.get("password", None)
try:
# manager.connect 负责处理连接逻辑,如果条件不满足,它会抛出 WebSocketDisconnect
await manager.connect(websocket, room_name, password)
# 获取客户端ID
client_id = websocket.scope["client_id"]
while True:
# 持续接收客户端消息
data = await websocket.receive_json()
await manager.handle_message(room_name, client_id, data)
except WebSocketDisconnect:
# 连接断开时,从管理器中移除客户端
await manager.remove(websocket)关键点在于manager.connect(websocket, room_name, password)方法。根据描述,如果连接不被允许(例如,房间不存在),此方法会立即抛出WebSocketDisconnect。当manager.connect抛出此异常时,websocket_endpoint中的except WebSocketDisconnect块会被执行,进而调用manager.remove(websocket),并在内部可能调用websocket.close()来显式关闭服务器端的WebSocket连接。
既然WebSocketDisconnect是在对已关闭连接进行操作时抛出的,那么解决方案就是:在连接建立后,立即尝试从客户端接收数据。如果服务器确实在没有发送任何数据的情况下关闭了连接,客户端的ws.receive_json()操作将立即感知到连接的关闭,并抛出WebSocketDisconnect。
以下是修正后的测试代码:
import pytest
from fastapi.testclient import TestClient
from fastapi.websockets import WebSocketDisconnect
# 假设 src.game_manager 和 src.main 已正确导入
from src.game_manager import GameManager, get_manager
from src.main import app
# 依赖覆盖,用于测试环境
async def override_manager() -> GameManager:
try:
yield override_manager.manager
except AttributeError:
manager = GameManager()
manager.add_new_game(max_players=2, room_name="foo", password=None)
manager.add_new_game(max_players=2, room_name="bar", password="123")
override_manager.manager = manager
yield override_manager.manager
app.dependency_overrides[get_manager] = override_manager
client = TestClient(app)
class TestWebsocketConnection:
def test_connect_to_non_existing_room_correct(self):
# 期望 WebSocketDisconnect 异常被抛出
with pytest.raises(WebSocketDisconnect):
# 建立 WebSocket 连接
with client.websocket_connect("/ws/non_existing_room") as ws:
# 尝试接收数据。由于服务器会立即关闭连接,此操作会抛出 WebSocketDisconnect
ws.receive_json()
解析:
通过在pytest.raises(WebSocketDisconnect)块内尝试从客户端的WebSocket连接接收数据,我们能够可靠地测试FastAPI应用中WebSocket连接是否按预期关闭。这种方法利用了WebSocketDisconnect异常的触发机制,即在尝试与已关闭连接交互时抛出,从而有效地验证了服务器端立即拒绝并终止连接的业务逻辑。掌握这一技巧,对于构建健壮且可测试的FastAPI WebSocket应用至关重要。
以上就是FastAPI WebSocket连接关闭测试:Pytest实战指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号