Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to patch with decorator using unittest_run_loop #4695

Closed
WisdomPill opened this issue Apr 17, 2020 · 5 comments
Closed

Unable to patch with decorator using unittest_run_loop #4695

WisdomPill opened this issue Apr 17, 2020 · 5 comments
Labels

Comments

@WisdomPill
Copy link
Member

馃悶 Describe the bug

patch using decorators is not possible while using unittest_run_loop,
to overcome this issue patching with context manager is needed, it can be a real pain to do it multiple times in the same test case. The arguments are not passed to the unittest test case method.

馃挕 To Reproduce

from unittest.mock import patch

from aiohttp.test_utils import AioHTTPTestCase, make_mocked_coro, unittest_run_loop
from aiohttp.web_app import Application
from aiohttp.web_request import Request
from aiohttp.web_response import Response
from aiohttp.web_routedef import get


async def do_something():
    print('something')


async def ping(request: Request) -> Response:
    await do_something()
    return Response(text='pong')


class TestApplication(AioHTTPTestCase):
    def get_app(self) -> Application:
        app = Application()
        app.router.add_routes([
            get('/ping/', ping)
        ])

        return app

    @unittest_run_loop
    async def test_ping(self):
        resp = await self.client.get('/ping/')

        self.assertEqual(resp.status, 200)
        self.assertEqual(await resp.text(), 'pong')

    @unittest_run_loop
    async def test_ping_mocked_do_something(self):
        with patch('test_bug.do_something', make_mocked_coro()) as do_something_patch:
            resp = await self.client.get('/ping/')

            self.assertEqual(resp.status, 200)
            self.assertEqual(await resp.text(), 'pong')

            self.assertTrue(do_something_patch.called)

    @unittest_run_loop
    @patch('test_bug.do_something', make_mocked_coro())
    async def test_ping_mocked_do_something_decorated(self, do_something_patch):
        resp = await self.client.get('/ping/')

        self.assertEqual(resp.status, 200)
        self.assertEqual(await resp.text(), 'pong')

        self.assertTrue(do_something_patch.called)

馃挕 Expected behavior

I expect to be able to use decorators to patch or mock a module/function

馃搵 Logs/tracebacks

something


Ran 3 tests in 0.080s

FAILED (errors=1)

Error
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 628, in run
    testMethod()
  File "/Users/anas/.envs/timing_router/lib/python3.7/site-packages/aiohttp/test_utils.py", line 488, in new_func
    func(self, *inner_args, **inner_kwargs))
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py", line 1255, in patched
    return func(*args, **keywargs)
TypeError: test_ping_mocked_do_something_decorated() missing 1 required positional argument: 'do_something_patch'

馃搵 Your version of the Python

$ python --version
Python 3.7.6

馃搵 Your version of the aiohttp/yarl/multidict distributions

$ python -m pip show aiohttp
Name: aiohttp
Version: 3.6.2
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
Author: Nikolay Kim
Author-email: fafhrd91@gmail.com
License: Apache 2
Location: /Users/anas/.envs/timing_router/lib/python3.7/site-packages
Requires: chardet, yarl, attrs, multidict, async-timeout
Required-by: 
$ python -m pip show multidict
Name: multidict
Version: 4.7.5
Summary: multidict implementation
Home-page: https://github.com/aio-libs/multidict
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /Users/anas/.envs/timing_router/lib/python3.7/site-packages
Requires: 
Required-by: yarl, aiohttp
$ python -m pip show yarl
Name: yarl
Version: 1.4.2
Summary: Yet another URL library
Home-page: https://github.com/aio-libs/yarl/
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /Users/anas/.envs/timing_router/lib/python3.7/site-packages
Requires: idna, multidict
Required-by: aiohttp

馃搵 Additional context

macOS catalina
aiohttp - server

@WisdomPill WisdomPill added the bug label Apr 17, 2020
@webknjaz
Copy link
Member

webknjaz commented Apr 17, 2020

This is the first time I see anybody using unittest and not pytest... Feel free to send a PR and we'll see what we can do. This looks like it may be a problem of unittest.mock.patch itself. Also, have you tried changing the order of decorators?

@WisdomPill
Copy link
Member Author

WisdomPill commented Apr 17, 2020

Yes, I see that many people prefer pytest over unittest, but I like to keep things simple and not import new dependencies and using new frameworks when python has them built in.

Changing the order of decorators does not help. What I discovered is that it is a problem of make_mocked_coro since MagicMock that cannot be awaited.

I found out that from python 3.8 unittest is asyncio capable with testcases, mocks and so on.
I had a dig in python bug tracker here https://bugs.python.org/issue26467 and I found this package that solved my problems. https://github.com/Martiusweb/asynctest/
It has some nice things like assert_awaited_once, await assert* and so on. From a brief look it seems like it behaves like the AsyncMock class introduced in python 3.8 available here https://docs.python.org/3.8/library/unittest.mock-examples.html

Maybe it can be added in the documentation that this package helps people that want to keep using unittest and do not have python 3.8 yet, describing how to use it. I could send a small example to show how to use it and if it fits a PR as well.

I would also suggest subclassing asynctest.TestCase from the same library and adding TestServer and TestClient since it also comes with cleaner asyncio testing capabilities.

I would be glad to improve the testing in this manner, but I also know, it is new library to add to the dependencies and that python3.8 is covered by python itself.

Here is the example by the way.

from functools import wraps
from typing import Any

from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from aiohttp.web_app import Application
from aiohttp.web_request import Request
from aiohttp.web_response import Response
from aiohttp.web_routedef import get
from asynctest.mock import patch


async def do_something():
    print('something')


async def ping(request: Request) -> Response:
    await do_something()
    return Response(text='pong')


class TestApplication(AioHTTPTestCase):
    def get_app(self) -> Application:
        app = Application()
        app.router.add_routes([
            get('/ping/', ping)
        ])

        return app

    @unittest_run_loop
    async def test_ping(self):
        resp = await self.client.get('/ping/')

        self.assertEqual(resp.status, 200)
        self.assertEqual(await resp.text(), 'pong')

    @unittest_run_loop
    async def test_ping_mocked_do_something(self):
        with patch('test_bug.do_something') as do_something_patch:
            resp = await self.client.get('/ping/')

            self.assertEqual(resp.status, 200)
            self.assertEqual(await resp.text(), 'pong')

            self.assertTrue(do_something_patch.called)

    @unittest_run_loop
    @patch('test_bug.do_something')
    async def test_ping_mocked_do_something_decorated(self, do_something_patch):
        resp = await self.client.get('/ping/')

        self.assertEqual(resp.status, 200)
        # self.assertEqual(await resp.text(), 'pong')

        self.assertTrue(do_something_patch.called)

@webknjaz
Copy link
Member

Feel free to send a docs PR then.

P.S. I personally recommend you to try out pytest anyway because it's easier to use and it's much more powerful in many ways.

@WisdomPill
Copy link
Member Author

@webknjaz can I close this? maybe we can discuss about a more async friendly unittest in #4700 I saw that even @asvetlov contributed to IsolatedAsyncTestCase, maybe you can give us a better understanding on that class.

@webknjaz
Copy link
Member

Sure, closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants