How to use setUp from pytest as an async method? - python

I have the following code:
import asyncio
import pytest
from mymodule import myasyncfunction
from unittest import TestCase
class TestDummy(TestCase):
def setUp(self):
await myasyncfunction()
#pytest.mark.asyncio
async def test_dummy(self):
assert False
The test passes because it doesn't enter the test at all. It only says:
RuntimeWarning: coroutine 'TestDummy.setUp' was never awaited
How to make the setUp function async?
Observation: If I remove the inheritance from TestCase the test runs but it won't enter the setUp function before, which is needed.

The solution is to define a method as a fixture instead of using the traditional setUp() method.
import pytest
class TestClass:
#pytest.fixture
def setup(self):
pass
#pytest.mark.asyncio
async def test_some_stuff(setup):
pass
As you discovered, with pytest-asyncio the setUp() method doesn't work when a class inherits from Unitest.Testcase:
TestPersonClass is not a child class of unittest.TestCase. If it was, the test would still succeed – but the success would be a false positive because code after the await expression would not run.
Why is this happening? The answer is complex enough that it deserves a
separate post, but the tl;dr version is that on line 93 of
pytest-asyncio’s source the author is expecting the event loop to be
passed into the test from a pytest fixture, while unittest.TestCase
methods cannot directly receive fixture function arguments.
For the above explanation see end of this blog post:
https://jacobbridges.github.io/post/unit-testing-with-asyncio/
For some decent tutorials on testing with pytest-asyncio see:
1)https://www.roguelynn.com/words/asyncio-testing/
2)https://medium.com/ideas-at-igenius/testing-asyncio-python-code-with-pytest-a2f3628f82bc

I encountered this issue today. In my case, it was an easy fix using this pattern:
class TestDummy:
def setup_class(self) -> None:
asyncio.run(do_server_registration_setup_stuff())
# other sync setup logic...
#pytest.mark.asyncio
async def test_some_stuff(setup):
pass
This may not suit your needs but for me, I am doing functional testing and so just needed to use aiohttp to run some http POST requests to register account settings etc. before the tests are run...
If you need to pre-populate some in-memory caches on the TestDummy instance you could pass self to do_server_registration_setup_stuff and set instance attributes in there.
There are probably better ways, but maybe this can help somebody to just solve the immediate problem and keep it moving!

Related

Local import as pytest fixture?

I need to import some functions locally within my tests (yes, the code base can be designed better to avoid this necessity, but let's assume we can't do that).
That means the first line of all my tests within a module looks like in this example:
def test_something():
from worker import process_message
process_message()
Now I wanted to make this more DRY by creating the following fixture:
#pytest.fixture(scope="module", autouse=True)
def process_message():
from worker import process_message
return process_message
But I always get the error
Fixture "process_message" called directly. Fixtures are not meant to
be called directly, but are created automatically when test functions
request them as parameters. See
https://docs.pytest.org/en/stable/explanation/fixtures.html for more
information about fixtures, and
https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly
about how to update your code.
The linked documentation doesn't help me much.
How can I achieve what I want? I'd like to return the function handle obviously.
The reason this happens is that you are calling the fixture directly from one of your tests. I assume that your test code with the fixture looks something like this:
import pytest
#pytest.fixture(scope="module", autouse=True)
def process_message():
from worker import process_message
return process_message
def test_something():
process_message()
And then test_something fails with the specified exception.
The way to fix it is to add process_message as an argument to the test function, indicating that you are using it as a fixture:
def test_something(process_message):
process_message()
btw, since you have to specify process_message fixture in every test in order to call it, means there is no point in using the autouse=True and it can be removed.

How to mock singleton instance methods?

I have a module written roughyl as follows:
class __Foo:
def __init__(self):
self.__client = CeateClient() # a library function
async def call_database(self):
self.__client.do_thing()
foo = __Foo()
This is designed so that the entire server only has one instance of this client manage, so foo is created once and is then used by various modules.
I am required to now use pytest to, well, test everything, but reading online I cannot find the correct way to path this situation.
The class method call_database sends an API request to a different service, which as part of the test, I want to not actually happen.
The suggestions I see talk about patching the class method itself, but also the class is private so I couldn't really do that.
The point is, when testing a function in a different module, i.e.:
from foo import foo
async def bar():
await foo.call_database()
When testing the function bar, I want the call to the database to not happen.
How would I go about doing that?

Async Patching issue with static methods in Python

I'm having a problem trying to patch a static method, no matter what i've tried the original method is still being ran. I'll illustrate an example below.
file A
import B
from unittest.mock import patch
from unittest import mock
import asynctest
import unittest
class Test(asynctest.TestCase):
#patch('B.OT._func')
async def test(self, mock_func):
mock_func.return_value = mock.MagicMock()
await B.foo()
mock_func.assert_called_once()
if __name__ == '__main__':
unittest.main()
file B
from path.C import OT
async def foo():
print('B')
return OT._func()
path(folder)/file C
class OT:
#staticmethod
def _func():
print('c')
return True
Problem
I'm having issues mocking the return value for OT._func(). Whatever I try to patch in file A doesn't stop _func() from running in class B. If the test case is ran it prints out 'c' instead of patching the function. My desired outcome is to patch out _func and have it return something else.
Edit: The issue seems to be with the async portion of the test, if the async is removed it works. A workaround for now is using the context manager version (with keyword) to patch stuff.
After looking into it more it seems to be an issue with async and the '#patch' decorator specifically (and possible python 3.7). A simple workaround is to using patching alongside the 'with' keyword. This will bypass the patch decorator not working for async functions.
Alternatively, upgrading from python 3.7.2 to 3.8.2 also fixed the issue and allows patch decorators to work with async functions.

Unittest and mocks, how to reset them?

I am testing a class that needs a mock in the constructor, so I usually do this:
class TestActionManager(unittest.TestCase):
#patch('actionlib.SimpleActionClient', return_value=create_autospec(actionlib.SimpleActionClient))
def setUp(self, mock1):
self.action_manager = ActionManager()
Then in this class I add all the tests. So the first one is working fine
def test_1(self):
self.action_manager.f()
self.action_manager.f.assert_called_once()
But if I add another test and run both
def test_2(self):
self.action_manager.f()
self.action_manager.f.assert_called_once()
It says f has been called twice. I was expecting setUp to create a new ActionManager (and hence create a new mock) before starting every test, but it is clearly not happening, since the mock is somehow shared. Also I tried to do
def tearDown(self):
del self.action_manager
But it does not fix the problem.
I have read something related in
Python Testing - Reset all mocks?
where the solution is to use a different library (something that I would like to avoid)
and in Any way to reset a mocked method to its original state? - Python Mock - mock 1.0b1 where it is using different classes to do it.
Is there any possibility to reset the mock in the same class before or after every test?
BTW, this is a unittest question, not a pytest question.
Anyways,
I believe what you're looking for is reset_mock
Here's, in general, how it works:
def test_1(self):
f = MagicMock() # or whatever you're mocking
f()
f.assert_called_once()
f.reset_mock()
f()
f.assert_called_once()
The result will be PASSED
If you want to automate, then you store the mocked thing inside setUp, and in tearDown you call the mocked thing's .reset_mock() method.
def setUp(self, mock1):
self.mock1 = mock1
# ... do other things ...
def tearDown(self):
self.mock1.reset_mock()

Disable autouse fixtures on specific pytest marks

Is it possible to prevent the execution of "function scoped" fixtures with autouse=True on specific marks only?
I have the following fixture set to autouse so that all outgoing requests are automatically mocked out:
#pytest.fixture(autouse=True)
def no_requests(monkeypatch):
monkeypatch.setattr("requests.sessions.Session.request", MagicMock())
But I have a mark called endtoend that I use to define a series of tests that are allowed to make external requests for more robust end to end testing. I would like to inject no_requests in all tests (the vast majority), but not in tests like the following:
#pytest.mark.endtoend
def test_api_returns_ok():
assert make_request().status_code == 200
Is this possible?
You can also use the request object in your fixture to check the markers used on the test, and don't do anything if a specific marker is set:
import pytest
#pytest.fixture(autouse=True)
def autofixt(request):
if 'noautofixt' in request.keywords:
return
print("patching stuff")
def test1():
pass
#pytest.mark.noautofixt
def test2():
pass
Output with -vs:
x.py::test1 patching stuff
PASSED
x.py::test2 PASSED
In case you have your endtoend tests in specific modules or classes you could also just override the no_requests fixture locally, for example assuming you group all your integration tests in a file called end_to_end.py:
# test_end_to_end.py
#pytest.fixture(autouse=True)
def no_requests():
return
def test_api_returns_ok():
# Should make a real request.
assert make_request().status_code == 200
I wasn't able to find a way to disable fixtures with autouse=True, but I did find a way to revert the changes made in my no_requests fixture. monkeypatch has a method undo that reverts all patches made on the stack, so I was able to call it in my endtoend tests like so:
#pytest.mark.endtoend
def test_api_returns_ok(monkeypatch):
monkeypatch.undo()
assert make_request().status_code == 200
It would be difficult and probably not possible to cancel or change the autouse
You can't canel an autouse, being as it's autouse. Maybe you could do something to change the autouse fixture based on a mark's condition. But this would be hackish and difficult.
possibly with:
import pytest
from _pytest.mark import MarkInfo
I couldn't find a way to do this, but maybe the #pytest.fixture(autouse=True) could get the MarkInfo and if it came back 'endtoend' the fixture wouldn't set the attribute. But you would also have to set a condition in the fixture parameters.
i.e.: #pytest.fixture(True=MarkInfo, autouse=True). Something like that. But I couldn't find a way.
It's recommended that you organize tests to prevent this
You could just separate the no_requests from the endtoend tests by either:
limit the scope of your autouse fixture
put the no_requests into a class
Not make it an auto use, just pass it into the params of each def you need it
Like so:
class NoRequests:
#pytest.fixture(scope='module', autouse=True)
def no_requests(monkeypatch):
monkeypatch.setattr("requests.sessions.Session.request", MagicMock())
def test_no_request1(self):
# do stuff here
# and so on
This is good practice. Maybe a different organization could help
But in your case, it's probably easiest to monkeypatch.undo()

Categories