Проверьте, что потребительский метод может вызвать исключение с помощью Django Channels и pytest-asyncio.

Используя Django и Channels 2, у меня есть потребительский метод, к которому можно обращаться через группы каналов и который может вызывать исключения. Как этот тривиальный:

from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync

class DummyConsumer(WebsocketConsumer):
    def connect(self):
        async_to_sync(self.channel_layer.group_add)(
            "dummy",
            self.channel_name,
        )
        self.accept()

    def will_raise(self, event):
        raise ValueError('value error')

    def disconnect(self, code):
        async_to_sync(self.channel_layer.group_discard)(
            "dummy",
            self.channel_name,
        )

Я хочу проверить этот метод, используя pytest-asyncio. Поскольку можно поймать исключение сопрограммы с pytest.raisesЯ наивно подумал, что такого будет достаточно

import pytest
from channels.testing import WebsocketCommunicator
from channels.layers import get_channel_layer
from app.consumers import DummyConsumer
channel_layer = get_channel_layer()

@pytest.fixture
async def communicator():
    communicator = WebsocketCommunicator(DummyConsumer, "ws/dummy/")
    await communicator.connect()
    yield communicator
    await communicator.disconnect()

@pytest.mark.asyncio
async def test_will_raise(communicator):
    with pytest.raises(ValueError):
        await channel_layer.group_send('dummy', {
            'type': 'will_raise'
        })

Но тест проваливается довольно запутанным способом (усеченный вывод):

================== ERRORS ==================
___ ERROR at teardown of test_will_raise ___
...
>       raise ValueError('value error')
E       ValueError: value error

app/consumers.py:28: ValueError
================= FAILURES =================
_____________ test_will_raise ______________
...
            await channel_layer.group_send('dummy', {
>               'type': 'will_raise'
            })
E           Failed: DID NOT RAISE <class 'ValueError'>

app/tests_dummy.py:21: Failed
==== 1 failed, 1 error in 1.47 seconds =====

И что же мне делать? Является ли поднятие исключения из потребительского метода плохим дизайном?

1 ответ

Решение

channel_layer имеет два сайта. Один сайт, который отправляет данные в channel_layer и другой сайт, который получает данные. Отправляющий сайт не получает никакого ответа от принимающего сайта. Это означает, что если принимающий сайт вызывает исключение, отправляющий сайт его не видит.

В своем тесте вы тестируете отправляющий сайт. Отправляет сообщение channel_layer, но, как объяснено, это не вызывает исключения.

Чтобы проверить, возникает ли исключение, вы должны написать тест, который подключается к вашему потребителю. Это может выглядеть так:

channel_layer = get_channel_layer()

@pytest.mark.asyncio
async def test_will_raise():
    communicator = WebsocketCommunicator(DummyConsumer, "ws/dummy/")
    await communicator.connect()

    await channel_layer.group_send('dummy', {
            'type': 'will_raise'
        })

    with pytest.raises(ValueError):
        await communicator.wait()

Как видите, исключение не происходит при отправке в channel_layer, но на коммуникаторе, который слушает на channel_layer, Смотрите также: https://channels.readthedocs.io/en/latest/topics/testing.html

Также обратите внимание, что тест не вызывает communicator.disconnect(), Когда исключение происходит внутри коммуникатора, disconnect() не должен быть вызван. Смотрите второе предложение в зеленом поле "Важно" под заголовком: https://channels.readthedocs.io/en/latest/topics/testing.html.

Однако вам не нужно отключать (), если в вашем приложении уже возникла ошибка.

Другие вопросы по тегам