skip to Main Content

I need to test my telegram bot. To do this I need to create client user to ask my bot. I found telethon library which can do it. First I wrote a code example to ensure that authorisation and connection works and send test message to myself (imports omitted):

api_id = int(os.getenv("TELEGRAM_APP_ID"))
api_hash = os.getenv("TELEGRAM_APP_HASH")
session_str = os.getenv("TELETHON_SESSION")

async def main():
    client = TelegramClient(
        StringSession(session_str), api_id, api_hash,
        sequential_updates=True
    )
    await client.connect()
    async with client.conversation("@someuser") as conv:
        await conv.send_message('Hey, what is your name?')


if __name__ == "__main__":
    asyncio.run(main())

@someuser (me) successfully receives message. Okay, now I create a test with fixtures based on code above:

api_id = int(os.getenv("TELEGRAM_APP_ID"))
api_hash = os.getenv("TELEGRAM_APP_HASH")
session_str = os.getenv("TELETHON_SESSION")

@pytest.fixture(scope="session")
async def client():
    client = TelegramClient(
        StringSession(session_str), api_id, api_hash,
        sequential_updates=True
    )
    await client.connect()
    yield client
    await client.disconnect()


@pytest.mark.asyncio
async def test_start(client: TelegramClient):
    async with client.conversation("@someuser") as conv:
        await conv.send_message("Hey, what is your name?")

After running pytest received an error:

AttributeError: 'async_generator' object has no attribute 'conversation'

It seems client object returned from client fixture in "wrong" condition. Here is print(dir(client)):

['__aiter__', '__anext__', '__class__', '__class_getitem__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'aclose', 'ag_await', 'ag_code', 'ag_frame', 'ag_running', 'asend', 'athrow']

Where I loose "right" client object from generator in fixture?

3

Answers


  1. I would rather use anyio instead of pytest-asyncio

    • pip install anyio

    Try this:

    import pytest
    
    @pytest.fixture(scope="session")
    def anyio_backend():
        return "asyncio"
    
    @pytest.fixture(scope="session")
    async def conv():
        client = TelegramClient(
            StringSession(session_str), api_id, api_hash,
            sequential_updates=True
        )
        await client.connect()
        async with client.conversation("@someuser") as conv:
            yield conv
    
    @pytest.mark.anyio
    async def test_start(conv):
        await conv.send_message("Hey, what is your name?")
    
    Login or Signup to reply.
  2. Use @pytest_asyncio.fixture decorator in async fixtures according to documentation https://pypi.org/project/pytest-asyncio/#async-fixtures.

    Like this:

    import pytest_asyncio
    
    @pytest_asyncio.fixture(scope="session")
    async def client():
        ...
    
    Login or Signup to reply.
  3. You have two options:

    1. Set asyncio_mode to auto—see readthedocs and concepts—in which case you can omit @pytest.mark.asyncio and use @pytest.fixture for fixtures, OR
    2. Use @pytest_asyncio.fixture per Filip Hanes’ answer

    Option 1 is the easy way, but option 2 is available for those who want to run some async tests using custom async libraries (such as trio).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search