0

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.

buhtz
  • 10,774
  • 18
  • 76
  • 149
  • 1
    That's odd. Maybe you're using a different version of aiohttp, and it's expecting a slightly different argument list? Just throwing some ideas out there. – David Knipe Aug 21 '21 at 20:44
  • I added version information in the question. Does your comment mean that you are not able to reproduce the error with the same code? – buhtz Aug 22 '21 at 08:12
  • 1
    I haven't tried running it. – David Knipe Aug 23 '21 at 07:44

1 Answers1

1

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)
Daniil Ryzhkov
  • 7,416
  • 2
  • 41
  • 58
  • Thanks a lot for answering. I do not understand the term _routes relatively to the root of your application_. I am not aware that my application has a route. And I assume that our understanding of the term _application_ is different. I am sure there is a concept behind it but the problem is that I can not find it in the aiohttp docu or in a tutorial somewhere. I have the feeling that understanding `aiohttp` needs to much implicit knowledge I do not have. – buhtz Aug 22 '21 at 15:40
  • 1
    @buhtz in other words it is not supposed to be used as mocking instrument. It is designed to build an application server. When you declare an application it doesn't listen for connections unless you explicitly pass requests to it. – Daniil Ryzhkov Aug 22 '21 at 17:01
  • "routes relatively to the root of your application" means that you should have URLs like "/foo/bar" starting with "/". The same as in Django or Flask. – Daniil Ryzhkov Aug 22 '21 at 17:02
  • Thx for answer I do understand. But then it totally confuses me that it appears in the testing example in the aiohttp docs when it is not supposed to be used for mocking. !? – buhtz Aug 22 '21 at 20:50
  • Can you please give us a fully working solution code. It does not run here. Tried a lot of variations. And one of the problems is that the (3rd party package) `responses` is realted to the `requests` package and not to `aiohttp`. There is nothing about `async`. Please see https://stackoverflow.com/q/68862782/4865723 to get a better idea how the structure of my real world software looks like. – buhtz Aug 23 '21 at 07:41
  • 1
    in aiohttp example it is not used for mocking, it doesn't fake requests http hosts, it is directly referenced through `self.client` (which will use application that you created in `get_application`), if you need to use aiohttp and you want aiohttp to provide you mocked response for a specific URL, you will have to override (monkey patch) aiohttp methods – Daniil Ryzhkov Aug 23 '21 at 09:07
  • Then what does the example do? btw: Overriding aiohttp method's is not easy because of the `async` thing. – buhtz Aug 23 '21 at 09:44
  • 1
    it creates an instance of Application and calls for it directly, it pass requests directly to this instance – Daniil Ryzhkov Aug 23 '21 at 10:27
  • I am sure there is a gap in my mind. ;) I understand what you write but where is the (conceptual) connection to unittest? But btw: Maybe you have an idea how to "override aiohttp method's" in this question https://stackoverflow.com/q/68862782/4865723 – buhtz Aug 23 '21 at 11:27