Search code examples
pythonmongodbpytestaiohttp

Testing aiohttp & mongo with pytest


I have a simple coroutine register that accepts login and password as post arguments, then it goes into the database and so on. The problem I have is that I do not know how to test the coroutine.

I followed examples from https://aiohttp.readthedocs.io/en/latest/testing.html.

And everything seemed easy until I started writing tests myself.

Code for test_register.py

from main import make_app
pytest_plugins = 'aiohttp.pytest_plugin'


@pytest.fixture
def cli(loop, test_client):
    return loop.run_until_complete(test_client(make_app))

async def test_register(cli):
    resp = await cli.post('/register', data={'login': 'emil', 'password': 'qwerty'})
    assert resp.status == 200
    text = await resp.text()    

And register.py

from settings import db

async def register(request):
    post_data = await request.post()
    print('Gotta: ', post_data)
    login, password = post_data['login'], post_data['password']
    matches = await db.users.find({'login': login}).count()
    ...

main.py

from aiohttp import web
from routes import routes


def make_app(loop=None):
    app = web.Application(loop=loop)
    for route in routes:
        app.router.add_route(route.method, route.url, route.handler)
    return app


def main():
    web.run_app(make_app())


if __name__ == "__main__":
    main()

settings.py

from motor.motor_asyncio import AsyncIOMotorClient
DBNAME = 'testdb'
db = AsyncIOMotorClient()[DBNAME]

And then I ran py.test test_register.py and it got stuck on database operation matches = await db.users.find({'login': login}).count()


Solution

  • The root of your problem is global variable usage.

    I suggest the following changes:

    from aiohttp import web
    from motor.motor_asyncio import AsyncIOMotorClient
    from routes import routes
    
    def make_app(loop=None):
        app = web.Application(loop=loop)
        DBNAME = 'testdb'
        mongo = AsyncIOMotorClient(io_loop=loop)
        db = mongo[DBNAME]
        app['db'] = db
    
        async def cleanup(app):
            mongo.close()
    
        app.on_cleanup.append(cleanup)
    
        for route in routes:
            app.router.add_route(route.method, route.url, route.handler)
        return app
    

    register.py

    async def register(request):
        post_data = await request.post()
        print('Gotta: ', post_data)
        login, password = post_data['login'], post_data['password']
        matches = await request.app['db'].users.find(
            {'login': login}).count()
        ...
    

    Pushing common-used objects into application's storage is an appreciated way for handling database connections etc.