CraftSpider / dpytest

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

[Question] Is it possible to trigger on_ready/setup_hook events? #124

Closed regunakyle closed 1 year ago

regunakyle commented 1 year ago

Hi, my bot runs some code before the bot starts up (see the minimal example below). I noticed that both on_ready and setup_hook are not run when running tests. Is it possible to trigger them automatically/manually?

Documentation on setup_hook() of Discord.py

Minimal example:

import typing as ty

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

class Misc(commands.Cog):
    @commands.command()
    async def ping(self, ctx):
        await ctx.send("Pong !")

class discordBot(commands.Bot):
    def __init__(
        self,
        command_prefix: str,
        intents: discord.Intents,
        activity: discord.Game,
        description: str,
    ):
        super().__init__(
            command_prefix=command_prefix,
            intents=intents,
            activity=activity,
            description=description,
        )

    async def on_ready(self) -> None:
        await self.add_cog(Misc())
        # Other setup tasks...

@pytest_asyncio.fixture
async def bot() -> None:
    intents = discord.Intents.default()
    intents.members = True
    intents.message_content = True

    activity = discord.Game(name="/help")

    description = "description"

    bot = discordBot("!", intents, activity, description)

    ## Uncomment the following line to run the test successfully
    # await bot.add_cog(Misc())

    if isinstance(bot.loop, discord.client._LoopSentinel):
        await bot._async_setup_hook()

    dpytest.configure(bot)

    yield bot

    await dpytest.empty_queue() 

@pytest.mark.asyncio
async def test_ping(bot) -> None:
    await dpytest.message("!ping")
    # Fails because on_start() is not triggered, so the cog Misc() is not registered
    assert dpytest.verify().message().content("Pong !") 
Sergeileduc commented 1 year ago

hmmm, I don't really know. I don't think they are executed (you can probably test that by adding some prints, and run pytest with the -s option, etc... but I don't think it is executed.)

on the other hand, I think this is not a bad thing.

It is supposed to be "unit test".

So to not loading 36 cogs (that your real bot does) for unit testing a single cog or command is not a bad thing, eventually.

what I do, is manually adding the cog I want to test (if you allready have a bot fixture (which I recommand) in your conftest.py, if you have lot of cogs to test, it's the better option) :

from cogs import Misc

#########################
# Fixtures
#########################

# fixture for bot with Misc cog loaded will be used in all tests of the file.
@pytest_asyncio.fixture(autouse=True)
async def bot_misc(bot):
    await bot.add_cog(Misc(bot))
    dpytest.configure(bot)
    return bot

#########################
# Tests
#########################

@pytest.mark.asyncio
async def test_ping():
    await dpytest.message('!ping')
    assert dpytest.verify().message().content("pong !")

@pytest.mark.asyncio
async def test_say():
    await dpytest.message('!say Unit testing the say command')
    assert dpytest.verify().message().content("Unit testing the say command")

Something like that. Manually adding the cog you wanna test in each of your test file. It's not perfect (I don't know if you do a lot of things in your on_ready, but if it's just cogs, and maybe connect to a database, or stuff, yeah, I suppose you juste have to copy the code in the fixture)

ho and that's some old code of mine, maybe you can replace the return bot with the

    yield bot
    await dpytest.empty_queue()

So yeah, again, I don't think it would be wise to implement the full "on_ready" thing. That would be weird to load every cogs, etc... for a unit test. In my humble opinion.

regunakyle commented 1 year ago

I will try to restructure my tests. Thanks!