Search code examples
pythonpython-unittestaiohttp

aiohttp unittest with URLs not GET


I played a bit arround with the example in the aiohttp docs about unitttesting to find out what is going on in the example and how it works. And I think I still missunderstand some things.

I need to "simulate" the download of xml or html files from URLs. But the example code is about GET method because it does router.add_get(). But there is not router.add_url() or something like that.

So is wrong in my understanding?

The error is

$ python3 -m unittest org.py
E
======================================================================
ERROR: test_example (org.MyAppTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/aiohttp/test_utils.py", line 439, in setUp
    self.app = self.loop.run_until_complete(self.get_application())
  File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/home/user/share/work/aiotest/org.py", line 16, in get_application
    app.router.add_get('https://stackoverflow.com', hello)
  File "/usr/local/lib/python3.9/dist-packages/aiohttp/web_urldispatcher.py", line 1158, in add_get
    resource = self.add_resource(path, name=name)
  File "/usr/local/lib/python3.9/dist-packages/aiohttp/web_urldispatcher.py", line 1071, in add_resource
    raise ValueError("path should be started with / or be empty")
ValueError: path should be started with / or be empty

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

The example code

#!/usr/bin/env python3
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from aiohttp import web, ClientSession


class MyAppTestCase(AioHTTPTestCase):

    async def get_application(self):
        """
        Override the get_app method to return your application.
        """
        async def hello(request):
            return web.Response(text='Hello')

        app = web.Application()
        app.router.add_get('https://stackoverflow.com', hello)
        return app

    # the unittest_run_loop decorator can be used in tandem with
    # the AioHTTPTestCase to simplify running
    # tests that are asynchronous
    @unittest_run_loop
    async def test_example(self):
        async with ClientSession() as session:
            async with session.get('https://stackoverflow.com') as resp:
                assert resp.status == 200
                text = await resp.text()
                assert 'Hello' in text

EDIT: My Python is 3.9.2 (default, Feb 28 2021, 17:03:44) [GCC 10.2.1 20210110] and aiohttp is `3.7.4.post0' - both from Debian 11 (stable) repository.


Solution

  • You can't mock a specific address with aiohttp.web.Application() routers. It expect you to declare routes relatively to the root of your application like on an example you mentioned:

    class MyAppTestCase(AioHTTPTestCase):
    
        async def get_application(self):
            """
            Override the get_app method to return your application.
            """
            async def hello(request):
                return web.Response(text='Hello, world')
    
            app = web.Application()
            app.router.add_get('/', hello)
            return app
    

    You should either test an application that you create in get_application method using self.client reference and use relative paths:

    @unittest_run_loop
    async def test_example(self):
        resp = await self.client.request("GET", "/")
        assert resp.status == 200
        text = await resp.text()
        assert "Hello, world" in text
    

    Or (what you probably actually want) use responses library:

    @responses.activate
    def test_simple():
        responses.add(responses.GET, 'https://stackoverflow.com',
                      body='Hello', status=200)