CraftSpider / dpytest

A package that assists in writing tests for discord.py
MIT License
103 stars 24 forks source link

`dpytest.message` crashes with an AttributeError because user is None #94

Closed Diapolo10 closed 1 year ago

Diapolo10 commented 1 year ago

I have a very bare-bones test case that fails no matter what I try. The command it's supposed to be testing does work, because I can run it manually on Discord, so I'm led to believe I've simply made a mistake in my test code somewhere. But the documentation doesn't seem to suggest there's anything wrong.

First, the versions:

Next, the error message:

bot = <discord.ext.commands.bot.Bot object at 0x0000016CB7DDE740>

    @pytest.mark.asyncio
    async def test_normal(bot):  # pylint: disable=W0613
        """Tests posting the stats of a regular account"""

>       await dpytest.message(f"{COMMAND_PREFIX}07hs Ironman-Dia")

tests\test_hiscores.py:16:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _  
C:\Users\laril\AppData\Local\pypoetry\Cache\virtualenvs\growlery-bkGHd1ph-py3.10\lib\site-packages\discord\ext\test\runner.py:185: in message
    mes = back.make_message(content, member, channel, attachments=attachments)
C:\Users\laril\AppData\Local\pypoetry\Cache\virtualenvs\growlery-bkGHd1ph-py3.10\lib\site-packages\discord\ext\test\backend.py:841: in make_message
    data = facts.make_message_dict(
C:\Users\laril\AppData\Local\pypoetry\Cache\virtualenvs\growlery-bkGHd1ph-py3.10\lib\site-packages\discord\ext\test\factories.py:381: in make_message_dict
    'author': dict_from_user(author),
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _  

user = None

    def dict_from_user(user: discord.User) -> _types.JsonDict:
        out = {
>           'id': user.id,
            'username': user.name,
            'discriminator': user.discriminator,
            'avatar': user.avatar
        }
E       AttributeError: 'NoneType' object has no attribute 'id'

C:\Users\laril\AppData\Local\pypoetry\Cache\virtualenvs\growlery-bkGHd1ph-py3.10\lib\site-packages\discord\ext\test\factories.py:86: AttributeError
---------------------------------------------------------------------------------------------------------------------------------------- Captured log setup ---------------------------------------------------------------------------------------------------------------------------------------- 
WARNING  discord.client:client.py:263 PyNaCl is not installed, voice will NOT be supported

---------- coverage: platform win32, python 3.10.9-final-0 -----------
Coverage HTML written to dir tests/reports/coverage-html
Coverage XML written to file tests/reports/coverage.xml

===================================================================================================================================== short test summary info ====================================================================================================================================== 
FAILED tests/test_hiscores.py::test_normal - AttributeError: 'NoneType' object has no attribute 'id'

It's clear that this has something to do with the "user" sending the message being the problem, but while I've tried to create a dummy user and give it to the function call as a second argument (both positional and as a keyword argument), it didn't help. It either did nothing or gave me another error about an invalid keyword argument.

Here's the entire test file,

"""Tests hiscores functionality"""

import discord.ext.test as dpytest
import pytest

from growlery.config import (
    COMMAND_PREFIX,
)

# @pytest.mark.xfail
@pytest.mark.asyncio
async def test_normal(bot):  # pylint: disable=W0613
    """Tests posting the stats of a regular account"""

    await dpytest.message(f"{COMMAND_PREFIX}07hs Ironman-Dia")
    assert dpytest.verify().message().contains("VIEWING STATS FOR IRONMAN-DIA")  # pylint: disable=E1121

and conftest.py:

"""Contains global fixtures for unit tests"""

import discord
import pytest_asyncio
from discord.ext import commands, test as dpytest

from growlery.cogs import cog_list
from growlery.config import (
    COMMAND_PREFIX,
)

@pytest_asyncio.fixture
async def bot():
    """Creates a bot instance for testing"""

    intents = discord.Intents.default()
    intents.message_content = True

    _bot = commands.Bot(command_prefix=COMMAND_PREFIX, intents=intents)

    await _bot._async_setup_hook()  # pylint: disable=W0212

    for cog in cog_list:
        await _bot.add_cog(cog(_bot))

    dpytest.configure(_bot)

    return _bot

@pytest_asyncio.fixture(autouse=True)
async def cleanup():
    """Performs cleanup after every test, emptying the message queue"""

    yield
    await dpytest.empty_queue()

The full project can be found here, if you need to see the rest.

Since I didn't find anyone else complain about similar problems, I'm not sure if this is a bug in the package itself, but I figured it wouldn't hurt to try.

Sergeileduc commented 1 year ago

Hmm.

Difference is we have intents.members = True in our conftest.

Maybe try that ?

Diapolo10 commented 1 year ago

That seems to have been the main problem, yes. I'm not entirely sure why the members intent is required here, but I suppose it doesn't matter in unit testing.

Apparently there was also another problem as I erroneously wrote

assert dpytest.verify().message().contains("VIEWING STATS FOR IRONMAN-DIA")

and missed that there was apparently a method I missed. Swapping it for

assert dpytest.verify().message().contains().content("VIEWING STATS FOR IRONMAN-DIA")

fixed the issue, and now everything is hunky-dory.

Thanks for the help, and apologies for wasting everyone's time. I need to learn to read.