Aiohttp: Test Client Loop Error with Actual App

Created on 17 Aug 2018  ·  4Comments  ·  Source: aio-libs/aiohttp

Long story short

The suggested testing pattern requires boilerplate duplication of app code and doesn't show testing the app directly.

https://docs.aiohttp.org/en/stable/testing.html#pytest

Expected behaviour

I should be able to pass my app module directly to test_client to test my app.

Actual behaviour

The docs show building the app from handlers on the fly before each test. Passing the real app directly to test_client works fine the first time, then fails with "RuntimeError: web.Application instance initialized with different loop".

Steps to reproduce

# handler.py

async def foo():
    return 'foo'
# app.py

from aiohttp import web
from .handler import foo

app = web.Application()
app.add_routes([web.get('/foo/', foo)])
# test.py

from .app import app

async def test_foo_status(test_client):
    response = await client.get('/foo/')
    assert response.status == 200

async def test_foo_body(test_client):
    response = await client.get('/foo/')
    body = await response.text()
    assert body == 'foo'

Your environment

  • OS: Ubuntu 18.04
  • Python: 3.6.5
  • aiohttp: 3.3.2

Work around

I hacked around the issue for now by peeking at the source code and determining the cause of the error.

        if self._loop is not None and self._loop is not loop:
            raise RuntimeError(
                "web.Application instance initialized with different loop")

https://github.com/aio-libs/aiohttp/blob/e561eaa/aiohttp/web_app.py#L134

It is not immediately clear to my why this code is necessary. I could not find any context in the git history. Setting app._loop = None in my test cleanup method seems to avoid the problem without any negative side effects. Is this code actually necessary? Could this fix be integrated into test_client?

outdated

Most helpful comment

Application is a stateful object, it should be recreated for every test from unit test suite.
The application's lifespan should be shorter than event loop's one.

Hint: replace app.app with async def app.make_app() coroutine which returns a new app on every call.

All 4 comments

Application is a stateful object, it should be recreated for every test from unit test suite.
The application's lifespan should be shorter than event loop's one.

Hint: replace app.app with async def app.make_app() coroutine which returns a new app on every call.

Add def make_app() to app.py? That makes sense.

FWIW tornado promotes the make_app() pattern in their very first example. I don't think mocking the app in the test file is a good pattern for the docs to promote.

http://www.tornadoweb.org/en/stable/#hello-world

Honestly I don't think mocking any aiohttp object is a good pattern

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a [new issue] for related bugs.
If you feel like there's important points made in this discussion, please include those exceprts into that [new issue].

Was this page helpful?
0 / 5 - 0 ratings