djangopostgresqlpytestpython-asynciodjango-channels

Django Channels Postgres InterfaceError: connection already closed


I can't seem to wrap my head around the issue here. I'm writing tests for my Channel Consumers following the description in the docs. I'd normally use the Django default unittest but since Channels requires using pytest, I'm using it but will still like to maintain the class structure of writing tests.

I had issues with writing the setup method similar to unittest which I would use to initialize the test attributes. I tried method fixtures, class fixtures using autouse=true, but none of them seemed to work as I couldn't access the instance attributes in the test methods. Finally I just decided to write the set_up instance method and manually call it for each class. This is what I have so far:

@pytest.mark.django_db
@pytest.mark.asyncio
class TestChatConsumer(object):

    @database_sync_to_async
    def set_up(self):

        self.user = UserFactory()
        self.api_client = APIClientFactory()
        self.token = TokenFactory(user=self.user)

        self.credentials = {
            'AUTHORIZATION': self.token.key,
            ...
        }

        self.credentials_params = '&'.join(['{}={}'.format(k, v) for k, v in self.credentials.items()])
        self.authless_endpoint = '/api/v1/ws/'
        self.endpoint = self.authless_endpoint + '?' + self.credentials_params

    async def test_connect_without_credentials(self):
        await self.set_up()
        communicator = WebsocketCommunicator(application, self.authless_endpoint)
        connected, subprotocol = await communicator.connect()
        assert not connected

    async def test_connect_with_credentials(self):
        await self.set_up()
        communicator = WebsocketCommunicator(application, self.endpoint)
        connected, subprotocol = await communicator.connect()
        assert connected

The issue is that I keep getting psycopg2.InterfaceError: connection already closed when my consumer tries to access the database in it's code inside the connect method. It uses database_sync_to_async and works pretty well when I'm manually testing it.

Specifically, the line in the connect method that raises the error looks like this:

await database_sync_to_async(user.inbox.increment_connections)().

Not sure what exactly the issue is as database_sync_to_async should properly clean up old connections so that new ones can be properly created.

Any help will be very much appreciated.


Solution

  • Apparently I was looking at the wrong place. The issue was on this line:

    await database_sync_to_async(user.inbox.increment_connections)().

    inbox is a related manager so it tries to fetch it before the actual database_sync_to_async and it fails as it requires a db call.

    I tried this await database_sync_to_async(user).inbox.increment_connections() and it didn't work since it wraps a method, not an attribute so what I ended up doing was to use prefetch_related to get the inbox during the authentication. In that way user.inbox no longer requires a db call and it works fine