Martiusweb / asynctest

Enhance the standard unittest package with features for testing asyncio libraries
https://asynctest.readthedocs.org/
Apache License 2.0
309 stars 41 forks source link

patch abnormalities #121

Closed ja8zyjits closed 5 years ago

ja8zyjits commented 5 years ago

The problem

while patching and sub classing there seems to be a deviation from the normal unittests behaviour.

scritp.py

def run_something():
pass

test_script.py


import asynctest
import asyncio
import unittest
import script

class MainTest(asynctest.TestCase): def setUp(self): pass

@asynctest.patch("script.run_something")
async def test_main(self, mock_run):
    script.run_something()
    self.assertEqual(mock_run.call_count, 1)

class MainTest2(MainTest): def setUp(self): pass

if name=="main": unittest.main()


while running the code with `python -m unittest` iam gettting the following error

```shell
.F
======================================================================
FAIL: test_main (test_script.MainTest2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/jzy/work/asynctest_issue_virtual/lib/python3.6/site-packages/asynctest/case.py", line 297, in run
    self._run_test_method(testMethod)
  File "/home/jzy/work/asynctest_issue_virtual/lib/python3.6/site-packages/asynctest/case.py", line 354, in _run_test_method
    self.loop.run_until_complete(result)
  File "/home/jzy/work/asynctest_issue_virtual/lib/python3.6/site-packages/asynctest/case.py", line 224, in wrapper
    return method(*args, **kwargs)
  File "/usr/lib/python3.6/asyncio/base_events.py", line 473, in run_until_complete
    return future.result()
  File "/home/jzy/work/asynctest_issue_virtual/lib/python3.6/site-packages/asynctest/_awaitable.py", line 21, in wrapper
    return await coroutine(*args, **kwargs)
  File "/home/jzy/work/asynctest_issue_virtual/lib/python3.6/site-packages/asynctest/mock.py", line 1006, in __next__
    return self.gen.send(None)
  File "/home/jzy/work/asynctest_issue_virtual/scripts/test_script.py", line 13, in test_main
    self.assertEqual(mock_run.call_count, 1)
AssertionError: 2 != 1

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)

But the same code in unittest passes.

test_script.py


import asynctest
import asyncio
import unittest
import script

class MainTest(asynctest.TestCase): def setUp(self): pass

@unittest.mock.patch("script.run_something")
def test_main(self, mock_run):
    script.run_something()
    self.assertEqual(mock_run.call_count, 1)

class MainTest2(MainTest): def setUp(self): pass

if name=="main": unittest.main()


while running the code with `python -m unittest` iam gettting the following output

```shell
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

The workaround

Iam able to get around this issue by using the context managers instead of the decorators like

with asynctest.patch("script.run_something") as mock_something:

And the test succeeds

Support information

The question

Martiusweb commented 5 years ago

Hi,

Thanks for your report.

You're running the same decorated test method twice, and it re-uses the same mock object, which is indeed a bug since unittest issues a new mock object every time.

I'll work on it.

ja8zyjits commented 5 years ago

@Martiusweb Thanks for your prompt response.

Martiusweb commented 5 years ago

In fact, this issue raises other problems happening with concurrent executions of patched coroutines.

In particular, since several coroutines instances can live simultaneously, several instances of a patched coroutine will break each other.

The issue is not about re-using the same mock, but using the same patch for each coroutine instance. I'm not sure yet how to solve this: should the patchings be tied to the coroutine instance instead of the coroutine function?

The internal behavior of asynctest will likely change quite a lot, so I think it should not be released in a patch version. I'll merge the deprecation of python 3.4 first.