Martiusweb/asynctest

Exception raised when patch a coroutine classmethod

Closed this issue · 6 comments

I try to patch coroutinu classmethod, but it raise an exception:

here the code:

# target.py
class Target(object):

    @classmethod
    async def a(cls):
        return 123

    async def b(self):
        return 456


# test_target.py
import asynctest
from target import Target


class TargetTest(asynctest.TestCase):

    @asynctest.patch('target.Target.a')
    async def test_classmethod(self, mock):
        print(mock) # it print a MagicMock object, not a CoroutineMock object
        mock.return_value = 456
        rt = await Target.a()
        self.assertEqual(rt, 123)

    @asynctest.patch('target.Target.b')
    async def test_method(self, mock):
        print(mock)
        mock.return_value = 789
        t = Target()
        rt = await t.b()
        self.assertEqual(rt, 789)

if __name__ == '__main__':
    asynctest.main()

Here is the Exception:

root@xxx-VirtualBox:/code/# python3 test_target.py
<MagicMock name='a' id='140488891004632'>
E
======================================================================
ERROR: test_classmethod (__main__.TargetTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/asynctest/case.py", line 297, in run
    self._run_test_method(testMethod)
  File "/usr/local/lib/python3.6/dist-packages/asynctest/case.py", line 354, in _run_test_method
    self.loop.run_until_complete(result)
  File "/usr/local/lib/python3.6/dist-packages/asynctest/case.py", line 224, in wrapper
    return method(*args, **kwargs)
  File "/usr/lib/python3.6/asyncio/base_events.py", line 468, in run_until_complete
    return future.result()
  File "/usr/local/lib/python3.6/dist-packages/asynctest/_awaitable.py", line 21, in wrapper
    return await coroutine(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/asynctest/mock.py", line 985, in __next__
    return self.gen.send(None)
  File "test_target.py", line 11, in test_classmethod
    rt = await Target.a()
TypeError: object int can't be used in 'await' expression

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (errors=1)

Please tell me what's wrong?

With asynctest.patch you need to patch a class, not a method. In your case useasynctest.patch.object(target.Target, 'a').

@Kentzo No asynctest.patch.object found in the package

Which version of asynctest are you using? It exists in the current release:

$ virtualenv venv
$ . venv/bin/activate
$ pip install asynctest
$ python
Python 3.6.5 (default, May 11 2018, 04:00:52) 
[GCC 8.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import asynctest
>>> asynctest.patch
<function patch at 0x7f3207505ae8>
>>> asynctest.patch.object
<function _patch_object at 0x7f32075241e0>

I updated the package version and there is indeed that function.
But the error still appears. And It seem happened only when patching a coroutine classmethod.
I also debugged my code. I guess the problem may be here:

# mock.py line 857
def _update_new_callable(patcher, new, new_callable):
    if new == DEFAULT and not new_callable:
        if asyncio.iscoroutinefunction(patcher.get_original()[0]):
            patcher.new_callable = CoroutineMock
        else:
            patcher.new_callable = MagicMock

    return patcher

When I patch a coroutine classmethod, The return value of function patcher.get_original()[0] is not a coroutine function. But when I path a corutine instance method, It return the desired result. Hope this can helps.

I'm sorry to trouble you so much and forgive my pool English....

This is my test code:

import asynctest
from target import Target


class TargetTest(asynctest.TestCase):

    @asynctest.patch.object(Target, 'a')
    async def test_classmethod(self, mock):
        print('test_classmethod: {}'.format(mock))
        mock.return_value = 456
        rt = await Target.a()
        self.assertEqual(rt, 123)

    @asynctest.patch.object(Target, 'b')
    async def test_method(self, mock):
        print('test_method: {}'.format(mock))
        mock.return_value = 789
        t = Target()
        rt = await t.b()
        self.assertEqual(rt, 789)


if __name__ == '__main__':
    asynctest.main()

This is the trackback info:

root@xxx-VirtualBox:/code# python3 test_target.py
test_classmethod: <MagicMock name='a' id='140173711134280'>  <--- It should be a CoroutineMock  object
Etest_method: <CoroutineMock name='b' id='140173711259128'>
.
======================================================================
ERROR: test_classmethod (__main__.TargetTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/asynctest/case.py", line 297, in run
    self._run_test_method(testMethod)
  File "/usr/local/lib/python3.6/dist-packages/asynctest/case.py", line 354, in _run_test_method
    self.loop.run_until_complete(result)
  File "/usr/local/lib/python3.6/dist-packages/asynctest/case.py", line 224, in wrapper
    return method(*args, **kwargs)
  File "/usr/lib/python3.6/asyncio/base_events.py", line 468, in run_until_complete
    return future.result()
  File "/usr/local/lib/python3.6/dist-packages/asynctest/_awaitable.py", line 21, in wrapper
    return await coroutine(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/asynctest/mock.py", line 985, in __next__
    return self.gen.send(None)
  File "test_target.py", line 11, in test_classmethod
    rt = await Target.a()
TypeError: object int can't be used in 'await' expression

----------------------------------------------------------------------

Thanks for the detailed report. I'm looking at it.

Indeed there was a bug where you spotted it.
It is fixed in the pull request #95.

A new version (0.12.2) is being released, and works both with @patch("Target.a") and @path.object(Target, "a").