使用Pytest测试Discord Bot时触发AttributeError问题求助
解决Discord Bot测试中的AttributeError: '_MissingSentinel'问题
问题背景
我开发了一个Discord Bot,逻辑是先通过bot.get_guild()从缓存获取服务器(guild),如果拿不到就调用fetch_guild接口去获取。对应的工具函数代码如下:
async def get_or_fetch_guild(obj_id, bot, guild): guild = bot.get_guild(obj_id) status = 'SUCCESS' status_code = 200 if guild is None: try: guild = await bot.fetch_guild(obj_id) except discord.errors.NotFound: guild = None status = 'INVALID_GUILD_ID' status_code = 400 except discord.errors.Forbidden: guild = None status = 'FORBIDDEN' status_code = 403 except discord.errors.HTTPException: guild = None status = 'HTTP_EXCEPTION' status_code = 400 return guild, status, status_code
这段代码正常运行没问题,但给调用它的API写Pytest测试用例时,出现了如下错误:
if not self._global_over.is_set(): E AttributeError: '_MissingSentinel' object has no attribute 'is_set' venv/lib/python3.11/site-packages/discord/http.py:605: AttributeError
对应的API接口代码:
@router.get("/guild/{guild_id}/roles", response_model=role_schema.AllRoleData) async def get_all_roles( guild_id: int, ): print("get all roles api called") guild, status, status_code = await get_or_fetch(guild_id, "guild", bot) # try to get the guild through api call if not found in cache if guild is None: print(f"Guild not found with id: {guild_id}") raise HTTPException(status_code=status_code, detail=status)
触发错误的测试用例示例:
@pytest.mark.asyncio @patch("discord.Client.get_user") @patch("discord.utils.get") async def test_assign_role_invalid_guild( mock_get_user, mock_get, bot: Tuple[commands.Bot, str], test_client ): user = dpytest.get_config().members[0] role = dpytest.get_config().guilds[0].roles[0] # Create mock user, guild, member, and role objects mock_user = MagicMock(spec=discord.User, id=user.id, name="user") mock_member = MagicMock(spec=discord.Member, id=user.id, name="member") mock_role = MagicMock(spec=discord.Role, id=role.id, name="role") # Make member.add_roles an AsyncMock mock_member.add_roles = AsyncMock() # Set up the return values of get_user, get mock_get_user.return_value = mock_user mock_get.return_value = mock_role # Call the assign_role endpoint response = test_client.post( "/api/v1/dashboard/assign/roles", json={"discord_user_id": user.id, "discord_guild_id": "1234", "role_ids": [role.id]}, headers={"X-Sizzle-External-Client-Key": settings.SIZZLE_BOT_API_SERVER_KEY}, ) assert response.status_code == 400 assert "detail" in response.json() assert response.json()["detail"] == "INVALID_GUILD_ID"
解决方案
这个错误的根源是测试用例里的bot实例没有正确初始化Discord HTTP客户端的内部属性,导致调用fetch_guild时访问了未初始化的_global_over。下面是几种可行的解决方法:
1. Mock fetch_guild方法,跳过真实API调用
直接在测试用例中mockbot.fetch_guild,让它返回预期结果或抛出指定异常,不用实际发起HTTP请求。示例修改:
@pytest.mark.asyncio @patch("discord.Client.get_user") @patch("discord.utils.get") @patch("discord.Bot.fetch_guild") # 添加这个mock async def test_assign_role_invalid_guild( mock_fetch_guild, mock_get_user, mock_get, bot: Tuple[commands.Bot, str], test_client ): # 模拟找不到guild的场景,抛出NotFound异常 mock_fetch_guild.side_effect = discord.errors.NotFound(404, "Not Found") # 原测试代码...
2. 初始化Bot的HTTP客户端属性
如果必须让fetch_guild在测试中执行,可以手动初始化Bot的HTTP相关属性:
import asyncio from discord.http import HTTPClient # 在测试用例中添加这段初始化代码 bot.http = HTTPClient(bot) bot.http._global_over = asyncio.Event() bot.http._global_over.set()
3. 给工具函数添加测试环境分支
在工具函数中判断是否处于测试环境,直接返回预设结果,跳过真实的fetch_guild调用:
import os async def get_or_fetch_guild(obj_id, bot, guild): guild = bot.get_guild(obj_id) status = 'SUCCESS' status_code = 200 if guild is None: # 测试环境下直接返回预设的无效guild结果 if os.getenv("TEST_ENV") == "true": return None, 'INVALID_GUILD_ID', 400 try: guild = await bot.fetch_guild(obj_id) # 原异常处理代码...
内容的提问来源于stack exchange,提问作者Aarti Joshi
相关产品推荐
相关产品推荐

