使用PyTest测试FastAPI WebSocket连接的关闭:一种可靠的方法

使用pytest测试fastapi websocket连接的关闭:一种可靠的方法

本文探讨了在FastAPI应用中使用PyTest测试WebSocket连接异常关闭的方法。当服务器端基于业务逻辑(如连接到不存在的房间)主动关闭连接时,客户端测试需要验证此行为。通过在连接建立后尝试从已关闭的WebSocket接收数据,可以有效捕获并断言WebSocketDisconnect异常,从而确保连接关闭逻辑的正确性。

1. 引言:FastAPI WebSocket连接关闭测试的重要性

在开发基于FastAPI的WebSocket应用时,确保连接管理逻辑的健壮性至关重要。特别是在某些业务场景下,服务器可能需要根据客户端的请求条件(例如,尝试连接到不存在的资源或未经授权的访问)立即拒绝并关闭WebSocket连接。为了验证这些服务器端的主动关闭行为是否按预期工作,我们需要编写有效的单元测试。

FastAPI配合PyTest是常见的测试组合,但测试WebSocket连接的关闭状态有时会遇到挑战。传统的异常捕获方式可能无法直接捕捉到WebSocket连接的断开事件,尤其是在连接建立过程中或建立后立即断开的情况下。

2. 问题分析:初始尝试与困境

许多开发者在尝试测试WebSocket连接关闭时,可能会直观地将pytest.raises(WebSocketDisconnect)用于包裹整个WebSocket连接建立的代码块,例如:

import pytestfrom fastapi.testclient import TestClientfrom fastapi.websockets import WebSocketDisconnect# ... (FastAPI应用和依赖覆盖设置) ...client = TestClient(app)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 websockets.WebSocketDisconnect’>。其原因在于,WebSocketDisconnect异常通常不是在client.websocket_connect调用时立即抛出,而是在客户端尝试与一个已经断开的连接进行通信(如发送或接收数据)时才会被感知并抛出。

在上述场景中,如果服务器端在manager.connect方法中判断房间不存在并立即抛出WebSocketDisconnect(或者调用websocket.close()而不进行websocket.accept()),client.websocket_connect可能会成功返回一个WebSocket对象,但该对象背后代表的连接实际上已经被服务器关闭。此时,直接在with client.websocket_connect(…)块外部捕获异常是无效的,因为异常尚未在客户端代码中向上冒泡。

3. 解决方案:利用 receive_json() 触发断开异常

解决这个问题的关键在于理解WebSocketDisconnect异常的触发机制。当一个WebSocket连接在服务器端被关闭后,客户端尝试从该连接接收数据时,会感知到连接已关闭的状态,并抛出WebSocketDisconnect异常。我们可以利用这一特性来验证连接的关闭。

核心思路:

使用client.websocket_connect尝试建立连接。在连接建立后(即使服务器已立即关闭),尝试从该WebSocket对象接收数据(例如,ws.receive_json())。如果服务器确实关闭了连接,此接收操作将立即触发WebSocketDisconnect异常。使用pytest.raises(WebSocketDisconnect)来捕获并断言这个预期异常。

以下是实现此方案的测试代码:

import pytestfrom fastapi.testclient import TestClientfrom fastapi.websockets import WebSocketDisconnectfrom typing import Annotatedfrom fastapi import Depends, APIRouter, WebSocket# 假设你的应用结构如下,这里为了完整性提供示例# src/game_manager.pyclass GameManager:    def __init__(self):        self.games = {}    def add_new_game(self, max_players, room_name, password):        self.games[room_name] = {"max_players": max_players, "password": password, "clients": {}}    async def connect(self, websocket: WebSocket, room_name: str, password: str | None):        if room_name not in self.games:            # 关键:如果房间不存在,立即抛出 WebSocketDisconnect            raise WebSocketDisconnect(code=1008, reason="Room does not exist")        # 假设这里会处理密码验证等,并最终接受连接        await websocket.accept()        # 假设 client_id 是从某个地方生成的        client_id = f"client_{len(self.games[room_name]['clients'])}"        websocket.scope["client_id"] = client_id        self.games[room_name]["clients"][client_id] = websocket        print(f"Client {client_id} connected to {room_name}")    async def handle_message(self, room_name, client_id, data):        print(f"Received message from {client_id} in {room_name}: {data}")    async def remove(self, websocket: WebSocket):        # 实际的移除逻辑        print(f"Client {websocket.scope.get('client_id')} disconnected.")# src/main.pyfrom fastapi import FastAPIapp = FastAPI()router = APIRouter()def get_manager():    # 实际应用中可能是单例或依赖注入    return GameManager()@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:        await manager.connect(websocket, room_name, password)        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)app.include_router(router)# tests/test_websockets.pyasync 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# 假设 get_manager 是你的依赖注入函数from src.main import get_managerapp.dependency_overrides[get_manager] = override_managerclient = TestClient(app)class TestWebsocketConnection:    def test_connect_to_non_existing_room_solution(self):        # 使用 pytest.raises 包裹,并在连接建立后尝试接收数据        with pytest.raises(WebSocketDisconnect):            with client.websocket_connect("/ws/non_existing_room") as ws:                # 关键步骤:尝试从已关闭的连接接收数据                ws.receive_json()

在这个修正后的测试中,当client.websocket_connect(“/ws/non_existing_room”)被调用时,服务器端的manager.connect方法会因为房间不存在而抛出WebSocketDisconnect。由于这个异常发生在websocket.accept()之前,websocket.accept()不会被调用,连接实际上未完全建立。客户端的TestClient在此时可能仍然会返回一个WebSocket对象。然而,当客户端随后尝试调用ws.receive_json()时,它会发现底层连接已经关闭,从而在客户端侧抛出WebSocketDisconnect异常。pytest.raises就能成功捕获到这个异常,验证了服务器正确地拒绝了连接。

4. 服务器端行为的配合

为了使上述测试方法有效,服务器端的WebSocket处理逻辑必须与预期行为相符:

如果服务器不应允许连接: 服务器应在调用websocket.accept()之前,通过抛出WebSocketDisconnect异常或直接调用websocket.close()来拒绝连接。在我们的示例中,manager.connect方法在检测到房间不存在时直接抛出了WebSocketDisconnect,这正是测试所期望的。如果服务器在接受后立即关闭: 即使服务器调用了websocket.accept(),但随后立即调用websocket.close()且不发送任何数据,客户端的ws.receive_json()仍然会捕获到WebSocketDisconnect。

重要的是,服务器在关闭连接时,不应在关闭前发送任何JSON消息,否则ws.receive_json()可能会先接收到一条消息,而不是直接抛出异常。

5. 总结与最佳实践

测试FastAPI WebSocket连接的关闭状态,特别是当服务器主动拒绝连接时,需要巧妙地利用WebSocketDisconnect异常的触发时机。

核心策略: 在预期连接会立即关闭的场景下,建立连接后,尝试从WebSocket客户端接收数据(如ws.receive_json())。如果连接确实被服务器关闭,此操作将导致客户端抛出WebSocketDisconnect异常。服务器端配合: 确保服务器在拒绝连接时,要么在websocket.accept()之前抛出WebSocketDisconnect,要么在websocket.accept()之后立即关闭连接且不发送任何消息。PyTest用法: 使用with pytest.raises(WebSocketDisconnect):来精确捕获并验证这个预期的异常。

通过这种方法,我们可以编写出健壮且准确的测试,以验证FastAPI WebSocket应用在各种异常情况下的连接管理逻辑。

以上就是使用PyTest测试FastAPI WebSocket连接的关闭:一种可靠的方法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1374748.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Othello Negascout (PVS) 算法实现与优化指南
上一篇 2025年12月14日 14:25:28
Selenium ChromeDriver 初始化常见错误与解决方案
下一篇 2025年12月14日 14:25:40

相关推荐

  • 修复Django电商项目中AJAX过滤产品列表图片不显示问题

    在Django电商项目中,当使用AJAX动态加载过滤后的产品列表时,常遇到图片无法正常显示的问题。这通常是由于前端模板中图片加载方式(如data-setbg属性结合JavaScript库)与AJAX动态内容更新机制不兼容所致。解决方案是直接在AJAX返回的HTML中使用标准的标签来渲染图片,确保浏览…

    2026年5月10日
    000
  • 开源免费PHP工具 PHP开发效率提升利器

    推荐开源免费PHP开发工具以提升效率:VS Code、Sublime Text轻量高效,PhpStorm专业强大;调试用Xdebug、Kint、Ray;依赖管理选Composer;代码质量工具包括PHPStan、Psalm、PHP_CodeSniffer;数据库管理可用%ignore_a_1%MyA…

    2026年5月10日
    000
  • Matplotlib 地图中多类型图例的创建与优化

    Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化

    本教程旨在解决matplotlib地图可视化中,如何在一个图例中同时展示颜色块(如区域分类)和自定义标记(如特定兴趣点)的问题。文章详细介绍了当传统`patch`对象无法正确显示标记时,如何利用`matplotlib.lines.line2d`创建标记图例句柄,并将其与颜色块图例句柄合并,从而生成一…

    2026年5月10日 用户投稿
    100
  • Golang JSON序列化:控制敏感字段暴露的最佳实践

    本教程探讨golang中如何高效控制结构体字段在json序列化时的可见性。当需要将包含敏感信息的结构体数组转换为json响应时,通过利用`encoding/json`包提供的结构体标签,特别是`json:”-“`,可以轻松实现对特定字段的忽略,从而避免敏感数据泄露,确保api…

    2026年5月10日
    000
  • 比特币新手教程 比特币交易平台有哪些

    比特币是一种去中心化的数字货币,基于区块链技术实现点对点交易,具有匿名性、有限发行和不可篡改等特点;新手可通过交易所购买,P2P交易获得比特币,常用平台包括Binance、OKX和Huobi;交易流程包括注册账户、实名认证、绑定支付方式、充值法币并下单购买,可选择市价单或限价单;比特币存储方式有交易…

    2026年5月10日
    000
  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • Go语言mgo查询构建:深入理解bson.M与日期范围查询的正确实践

    本文旨在解决go语言mgo库中构建复杂查询时,特别是涉及嵌套`bson.m`和日期范围筛选的常见错误。我们将深入剖析`bson.m`的类型特性,解释为何直接索引`interface{}`会导致“invalid operation”错误,并提供一种推荐的、结构清晰的代码重构方案,以确保查询条件能够正确…

    2026年5月10日
    100
  • Golang goroutine与channel调试技巧

    使用go run -race检测数据竞争,结合runtime.NumGoroutine监控协程数量,通过pprof分析阻塞调用栈,利用select超时避免永久阻塞,有效排查goroutine泄漏、死锁和数据竞争问题。 Go语言的goroutine和channel是并发编程的核心,但它们也带来了调试上…

    2026年5月10日
    000
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 《魔兽世界》将于6月11日开启国服回归技术测试

    《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试

    《%ign%ignore_a_1%re_a_1%》官方宣布,将于6月11日开启国服回归技术测试,时间为7天,并称可以在6月内正式开服,玩家们可以访问官网下载战网客户端并预下载“巫妖王之怒”客户端,技术测试详情见下图。 WordAi WordAI是一个AI驱动的内容重写平台 53 查看详情 以上就是《…

    2026年5月10日 用户投稿
    200
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    000
  • 前端缓存策略与JavaScript存储管理

    根据数据特性选择合适的存储方式并制定清晰的读写与清理逻辑,能显著提升前端性能;合理运用Cookie、localStorage、sessionStorage、IndexedDB及Cache API,结合缓存策略与定期清理机制,可在保证用户体验的同时避免安全与性能隐患。 前端缓存和JavaScript存…

    2026年5月10日
    100
  • HTML5网页如何实现手势操作 HTML5网页移动端交互的处理技巧

    首先利用原生touch事件实现滑动判断,再通过preventDefault解决滚动冲突,接着引入Hammer.js处理复杂手势,最后通过优化点击区域、避免事件冲突和增加视觉反馈提升体验。 在移动端浏览器中,HTML5网页可以通过触摸事件实现手势操作,提升用户体验。虽然原生JavaScript提供了基…

    2026年5月10日
    000
  • 深入理解 Express.js 中 next() 参数的作用与中间件机制

    本文深入探讨 express.js 中间件函数中的 `next()` 参数。它负责将控制权传递给请求-响应周期中的下一个中间件或路由处理程序。文章将详细解释 `next()` 的工作原理、中间件的注册与执行顺序,以及不正确使用 `next()` 可能导致请求挂起的风险,并通过代码示例和实际应用场景,…

    2026年5月10日
    000
  • 创建指定大小并填充特定数据的Golang文件教程

    本文将介绍如何使用Golang创建一个指定大小的文件,并用特定数据填充它。我们将使用 `os` 包提供的函数来创建和截断文件,从而实现快速生成大文件的目的。示例代码展示了如何创建一个10MB的文件,并将其填充为全零数据。掌握这些方法,可以方便地在例如日志系统或磁盘队列等场景中,预先创建测试文件或初始…

    2026年5月10日
    000
  • Python命令怎样使用profile分析脚本性能 Python命令性能分析的基础教程

    使用Python的cProfile模块分析脚本性能最直接的方式是通过命令行执行python -m cProfile your_script.py,它会输出每个函数的调用次数、总耗时、累积耗时等关键指标,帮助定位性能瓶颈;为进一步分析,可将结果保存为文件python -m cProfile -o ou…

    2026年5月10日
    000
  • 使用 WebCodecs VideoDecoder 实现精确逐帧回退

    本文档旨在解决在使用 WebCodecs VideoDecoder 进行视频解码时,实现精确逐帧回退的问题。通过比较帧的时间戳与目标帧的时间戳,可以避免渲染中间帧,从而提高用户体验。本文将提供详细的解决方案和示例代码,帮助开发者实现精确的视频帧控制。 在使用 WebCodecs VideoDecod…

    2026年5月10日
    000
  • 如何插入查询结果数据_SQL插入Select查询结果方法

    如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法

    使用INSERT INTO…SELECT语句可高效插入数据,通过NOT EXISTS、LEFT JOIN、MERGE语句或唯一约束避免重复;表结构不一致时可通过别名、类型转换、默认值或计算字段处理;结合存储过程可提升可维护性,支持参数化与动态SQL。 将查询结果数据插入到另一个表中,可以…

    2026年5月10日 用户投稿
    000
  • Discord.py 交互按钮超时与持久化解决方案

    本教程旨在解决Discord.py中交互按钮在一段时间后出现“This Interaction Failed”错误的问题。我们将深入探讨视图(View)的超时机制,并提供通过正确设置timeout参数以及利用bot.add_view()方法实现按钮持久化的具体方案,确保您的机器人交互功能稳定可靠,即…

    2026年5月10日
    000
  • Debian Copilot的社区活跃度如何

    debian copilot是codeberg社区维护的ai助手,旨在为debian用户提供服务。尽管搜索结果中没有直接提供关于debian copilot社区支持活跃度的具体数据,但我们可以通过debian社区的整体活跃度和特点来推断其活跃性。 Debian社区的一般情况: Debian拥有详尽的…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信