Let's say I have a very simple logging decorator:
from functools import wraps
def my_decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__} ran with args: {args}, and kwargs: {kwargs}")
result = func(*args, **kwargs)
return result
return wrapper
I can add this decorator to every pytest unit test individually:
#my_decorator
def test_one():
assert True
#my_decorator
def test_two():
assert 1
How can I automatically add this decorator to every single pytest unit test so I don't have to add it manually? What if I want to add it to every unit test in a file? Or in a module?
My use case is to wrap every test function with a SQL profiler, so inefficient ORM code raises an error. Using a pytest fixture should work, but I have thousands of tests so it would be nice to apply the wrapper automatically instead of adding the fixture to every single test. Additionally, there may be a module or two I don't want to profile so being able to opt-in or opt-out an entire file or module would be helpful.
Provided you can move the logic into a fixture, as stated in the question, you can just use an auto-use fixture defined in the top-level conftest.py.
To add the possibility to opt out for some tests, you can define a marker that will be added to the tests that should not use the fixture, and check that marker in the fixture, e.g. something like this:
conftest.py
import pytest
def pytest_configure(config):
config.addinivalue_line(
"markers",
"no_profiling: mark test to not use sql profiling"
)
#pytest.fixture(autouse=True)
def sql_profiling(request):
if not request.node.get_closest_marker("no_profiling"):
# do the profiling
yield
test.py
import pytest
def test1():
pass # will use profiling
#pytest.mark.no_profiling
def test2():
pass # will not use profiling
As pointed out by #hoefling, you could also disable the fixture for a whole module by adding:
pytestmark = pytest.mark.no_profiling
in the module. That will add the marker to all contained tests.
Related
Similar questions were asked before:
How to skip or ignore python decorators
Bypassing a decorator for unit testing
functools.wraps creates a magic method called __wrapped__ in the decorator, which allows to access the wrapped function. When you have multiple decorators, though, this would return an inner decorator, not the original function.
How to avoid or bypass multiple decorators?
To bypass or avoid multiple decorators and access the inner most function, use this recursive method:
def unwrap(func):
if not hasattr(func, '__wrapped__'):
return func
return unwrap(func.__wrapped__)
In pytest, you can have this function in conftest.py and access throughout your tests with:
# conftest.py
import pytest
#pytest.fixture
def unwrap():
def unwrapper(func):
if not hasattr(func, '__wrapped__'):
return func
return unwrapper(func.__wrapped__)
yield unwrapper
# my_unit_test.py
from my_module import decorated_function
def test_my_function(unwrap):
decorated_function_unwrapped = unwrap(decorated_function)
assert decorated_function_unwrapped() == 'something'
I would like to know if there is a way to access a pytest fixture from functions located in other modules. I wonder if I could use custom pytest hooks for this.
# test/test_file.py
pytest.fixture
def my_fixture()
return my_object
def test_function():
print('Starting test)
my_function('arg1')
# lib/functions.py
def my_function(arg1):
# I would like have access to my_fixture
So since it's possible to access fixtures in pytest hooks during runtime session, I wonder if there is a way to access them from outside of the test functions also.
So far this solution works for me:
#pytest.mark.hookwrapper
def pytest_runtest_makereport(self, item, call):
# Stops at first non-None test result
outcome = yield
rep = outcome.get_result()
if rep.when == 'call' and hasattr(item, 'callspec'):
test_case = item.callspec.params.get('test_case')
Here I can access my_fixture thingy, but only in a pre-defined hooks, pytest standard flow. I wonder if I could create my own hooks, and then make my functions "aware" of these and get the access to anything defined inside.
How to define and use custom hooks?
I have being trying to find a way to use mocking decorators and pytest capsys at the same time but I wasn't able to find the right way to do it.
import pytest
import requests_mock
#requests_mock.mock()
def test_with_mock(m):
pass
def test_with_capsys(capsys):
pass
# how to write a test that works with both?
As stated in the request-mock's docs:
pytest has its own method of registering and loading custom fixtures. requests-mock provides an external fixture registered with pytest such that it is usable simply by specifying it as a parameter. There is no need to import requests-mock it simply needs to be installed and specify the argument requests_mock.
The fixture then provides the same interface as the requests_mock.Mocker letting you use requests-mock as you would expect.
>>> import pytest
>>> import requests
>>> def test_url(requests_mock):
... requests_mock.get('http://test.com', text='data')
... assert 'data' == requests.get('http://test.com').text
...
So just use the requests_mock fixture instead of the decorator:
def test_with_mock_and_capsys(requests_mock, capsys):
pass
Background
pytest doesn't play along with function decorators that add positional arguments to the test function. pytest considers all arguments that
aren't bound to an instance or type as in instance or class methods;
don't have default values;
aren't bound with functools.partial;
aren't replaced with unittest.mock mocks
to be replaced with fixture values, and will fail if it doesn't find a suitable fixture for any argument. So stuff like
import functools
import pytest
def deco(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
args += ('spam',)
return func(*args, **kwargs)
return wrapper
#deco
def test_spam(spam_arg):
assert True
will fail, and this is exactly what requests-mock does. A workaround to that would be passing the mocker via keyword args:
import pytest
import requests_mock
#requests_mock.Mocker(kw='m')
def test_with_mock_and_fixtures(capsys, **kwargs):
m = kwargs['m']
...
but since requests-mock already offers a fixture, why bother using the decorator?
The baseline of all my tests is that there will always be a taxi with at least one passenger in it. I can easily achieve this setup with some basic fixtures:
from blah import Passenger, Taxi
#pytest.fixture
def passenger():
return Passenger()
#pytest.fixture
def taxi(passenger):
return Taxi(rear_seat=passenger)
Testing the baseline is straightforward:
def test_taxi_contains_passenger(taxi)
assert taxi.has_passenger()
My issue crops up when I start needing more complicated test setup. There will be scenarios where I'll need the taxi to have more than one passenger and scenarios where I'll need to define passenger attributes. For example:
def test_three_passengers_in_taxi(taxi)
assert taxi.has_passengers(3)
assert taxi.front_passenger_is_not_a_child()
I'm able to get around this problem by having specific fixtures for specific tests. For the above test, I would create the following fixture:
#pytest.fixture
def three_passenger_test_setup(taxi)
taxi.add_front_seat_passenger(Passenger(child=False))
taxi.add_rear_seat_passenger(Passenger())
return taxi
I can pass the above fixture into my test case and everything is dandy, but if I go down this route I might end up with a fixture for every test and it feels like there should be a more efficient way of doing this.
Is there a way to pass arguments to a fixture so that those arguments can be used in creating the object the fixture returns? Should I be parameterizing the test function? The fixture? Or am I wasting time and is a fixture per test the way to go?
We can do this by using a method that takes args within a fixture and return the method from the fixture.
let me show you an example
#pytest.fixture
def my_fixture():
def _method(a, b):
return a*b
return _method
def test_me(my_fixture):
result1 = my_fixture(2, 3)
assert result1 == 6
result2 = my_fixture(4, 5)
assert result2 == 20
Is there a way to pass arguments to a fixture so that those arguments
can be used in creating the object the fixture returns?
Should I be parameterizing the test function?
You can use test parametrization with indirect=True.
In the pytest docs: Apply indirect on particular arguments.
As displayed here: https://stackoverflow.com/a/33879151/3858507
The fixture?
Another option that might suit you is using some fixture that specifies the argument using parametrization:
#pytest.fixture(params=[3,4])
def number_of_passengers(request):
return request.param
and then accessing this fixture from the taxi and the test itself:
#pytest.fixture
def taxi(number_of_passengers):
return Taxi(rear_seat=Passenger() * number_of_passengers)
def test_three_passengers_in_taxi(taxi, number_of_passengers)
assert taxi.has_passengers(number_of_passengers)
assert taxi.front_passenger_is_not_a_child()
This way is good if your tests and asserts are very similar between the cases you have.
Or am I wasting time and is a fixture per test the way to go?
I'd say you definitely shouldn't create a fixture for every test function. For that, you can just put the setup inside the test. This is actually a viable alternative in the case that you have to make different asserts for different cases of the taxi.
And finally another possible pattern you can use is a taxi factory. While for the example you've presented its not quite useful, if multiple parameters are required and only some are changing you can create a fixture similar to the following:
from functools import partial
#pytest.fixture
def taxi_factory():
return partial(Taxi, 1, 2, 3)
That fixture is just a Python decorator.
#decorator
def function(args):
...
is fancy for
def function(args):
...
function = decorator(function)
So you just might be able to write your own decorator, wrapping up the function you want to decorate in whatever you need and the fixture:
def myFixture(parameter):
def wrapper(function):
def wrapped(*args, **kwargs):
return function(parameter, *args, **kwargs)
return wrapped
return pytest.fixture(wrapper)
#myFixture('foo')
def function(parameter, ...):
...
This will act like the fixture but will pass a value ('foo') as parameter to function.
TLDR; Use pytest.mark and the request fixture to access request.keywords
This is a very old question, but existing answers did not work for me, so here is my solution using pytest.mark
from blah import Passenger, Taxi
#pytest.fixture
def passenger():
return Passenger()
#pytest.fixture
def taxi(passenger, request):
if "taxi" in request.keywords:
kwargs = request.keywords["taxi"].kwargs
else:
kwargs = dict(rear_seat=passenger)
return Taxi(**kwargs)
# This allows testing the baseline as-is...
def test_taxi_contains_passenger(taxi)
assert taxi.has_passenger()
# and also using pytest.mark to pass whatever kwargs:
#pytest.mark.taxi(rear_seat=[Passenger()] * 3)
def test_three_passengers_in_taxi(taxi)
assert taxi.has_passengers(3)
assert taxi.front_passenger_is_not_a_child()
I'm trying to use py.test's fixtures with my unit tests, in conjunction with unittest. I've put several fixtures in a conftest.py file at the top level of the project (as described here), decorated them with #pytest.fixture, and put their names as arguments to the test functions that require them.
The fixtures are registering correctly, as shown by py.test --fixtures test_stuff.py, but when I run py.test, I get NameError: global name 'my_fixture' is not defined. This appears to only occur when I use subclasses of unittest.TestCase—but the py.test docs seem to say that it plays well with unittest.
Why can't the tests see the fixtures when I use unittest.TestCase?
Doesn't work:
conftest.py
#pytest.fixture
def my_fixture():
return 'This is some fixture data'
test_stuff.py
import unittest
import pytest
class TestWithFixtures(unittest.TestCase):
def test_with_a_fixture(self, my_fixture):
print(my_fixture)
Works:
conftest.py
#pytest.fixture()
def my_fixture():
return 'This is some fixture data'
test_stuff.py
import pytest
class TestWithFixtures:
def test_with_a_fixture(self, my_fixture):
print(my_fixture)
I'm asking this question more out of curiosity; for now I'm just ditching unittest altogether.
While pytest supports receiving fixtures via test function arguments
for non-unittest test methods, unittest.TestCase methods cannot
directly receive fixture function arguments as implementing that is
likely to inflict on the ability to run general unittest.TestCase test
suites.
From the note section at the bottom of:
https://pytest.org/en/latest/unittest.html
It's possible to use fixtures with unittest.TestCasees. See that page for more information.
You can use the pytest fixtures in unittest.TestCase with the pytest option autouse. However, if you use the test_ for the unit method using the fixture the following error will appear:
Fixtures are not meant to be called directly,...
### conftest.py
#pytest.fixture
def my_fixture():
return 'This is some fixture data'
One solution is to use a prepare_fixture method to set fixtures as an attribute of the TestWithFixtures class, so that fixtures are available to all unit test methods.
### test_stuff.py
import unittest
import pytest
class TestWithFixtures(unittest.TestCase):
#pytest.fixture(autouse=True)
def prepare_fixture(self, my_fixture):
self.myfixture = my_fixture
def test_with_a_fixture(self):
print(self.myfixture)
define the fixture as an accessible variable, (like input in the following example). To define it, use request.cls.VARIABLE_NAME_YOU_DEFINE = RETURN_VALUE
use #pytest.mark.usefixtures("YOUR_FIXTURE") to use fixture outside of the unittest class, inside the unittest class, access the fixture by self.VARIABLE_NAME_YOU_DEFINE.
e.g.
import unittest
import pytest
#pytest.fixture(scope="class")
def test_input(request):
request.cls.input = {"key": "value"}
#pytest.mark.usefixtures("test_input")
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertEqual(self.input["key"], "value")