首页 > 解决方案 > Test that a consumer method can raise an exception with Django Channels and pytest-asyncio

问题描述

Using Django and Channels 2, I have a consumer method that can can be accessed through channel groups and that may raise exceptions. Like this trivial one:

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,
        )

I want to test this method using pytest-asyncio. Since one can catch the exception of a coroutine with pytest.raises, I thought naively that something like this would be enough:

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'
        })

But the test fails in a pretty confusing way (truncated output):

================== 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 =====

So, what should I do? Is the raising of an exception from a consumer method a bad design?

标签: pythonpython-3.xpython-asynciodjango-channelspytest-asyncio

解决方案


Achannel_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#wait

另请注意,测试不会调用communicator.disconnect(). 当通信器内部发生异常时,disconnect()不必调用。请参阅此标题下方绿色“重要”框中的第二句:https ://channels.readthedocs.io/en/latest/topics/testing.html#websocketcommunicator

但是,如果您的应用程序已经引发错误,则不必断开连接()。


推荐阅读