pytest access parameters from function scope fixture - python

Let's assume I have the following code:
#pytest.mark.parametrize("argument", [1])
def test_func(self, function_context, argument)
And I have the following function scope fixture:
#pytest.fixture(scope='function')
def function_context(session_context):
# .... do something ....
Is it possible to access the current function argument from within the function_context fixture?
In my case - I want to get the value 1 that is being passed in parametrize from within function_context.

Fixtures in pytest are instantiated before the actual tests are ran, so it shouldn't be possible to access the test function argument at the fixture definition stage. However, I can think of two ways to bypass this:
1. Monkeypatching
You can monkeypatch the fixture, i.e. temporarily change some of its attributes, based on the parameter of the function that uses this fixture. For example:
#pytest.fixture(scope='function')
def function_context(session_context):
# .... do something ....
#pytest.mark.parametrize("argument", [1])
def test_func(self, function_context, argument, monkeypatch):
monkeypatch.setattr(function_context, "number", argument) # assuming you want to change the attribute "number" of the function context
# .... do something ....
Although your fixture is valid for the scope of the function only anyhow, monkeypatching is also only valid for a single run of the test.
2. Parametrizing the fixture instead of the test function
Alternatively, you can also choose to parametrize the fixture itself instead of the test_func. For example:
#pytest.fixture(scope='function', params=[0, 1])
def function_context(session_context, request):
param = requests.param # now you can use param in the fixture
# .... do something ...
def test_func(self, function_context):
# .... do something ...

Related

How does a pytest mocker work given there is no import statement for it?

I am following this mini-tutorial/blog on pytest-mock. I can not understand how the mocker is working since there is no import for it - in particular the function declaration def test_mocking_constant_a(mocker):
import mock_examples.functions
from mock_examples.functions import double
def test_mocking_constant_a(mocker):
mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 2)
expected = 4
actual = double() # now it returns 4, not 2
assert expected == actual
Somehow the mocker has the attributes/functions of pytest-mocker.mocker: in particular mocker.patch.object . But how can that be without the import statement?
The mocker variable is a Pytest fixture. Rather than using imports, fixtures are supplied using dependency injection - that is, Pytest takes care of creating the mocker object for you and supplies it to the test function when it runs the test.
Pytest-mock defines the "mocker" fixture here, using the Pytest fixture decorator. Here, the fixture decorator is used as a regular function, which is a slightly unusual way of doing it. A more typical way of using the fixture decorator would look something like this:
#pytest.fixture()
def mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]:
"""
Return an object that has the same interface to the `mock` module, but
takes care of automatically undoing all patches after each test method.
"""
result = MockerFixture(pytestconfig)
yield result
result.stopall()
The fixture decorator registers the "mocker" function with Pytest, and when Pytest runs a test with a parameter called "mocker", it inserts the result of the "mocker" function for you.
Pytest can do this because it uses Python's introspection features to view the list of arguments, complete with names, before calling the test function. It compares the names of the arguments with names of fixtures that have been registered, and if the names match, it supplies the corresponding object to that parameter of the test function.

Can I pass arguments to pytest fixtures?

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()

Report multiple tests from a single function with pytest

I currently have a simple test which instantiates a bunch of similar objects and executes a method to ensure the method does not throw any exceptions:
class TestTemplates(object):
def test_generate_all(self):
'''Generate all the templates and ensure none of them throw validation errors'''
for entry_point in pkg_resources.iter_entry_points('cloudformation.template'):
object = entry_point.load()
object().build().to_json()
This is reported in the text output of pytest as a single test:
test/test_templates.py::TestTemplates::test_generate_all PASSED
Also in the junit XML:
<testcase classname="test.test_templates.TestTemplates" file="test/test_templates.py" line="31" name="test_generate_all" time="0.0983951091766"></testcase>
Is it possible for each object tested to be reported as a separate test without manually defining a test function for each object?
I'd define your list of objects as a fixture, then pass that list to a parametrized test:
#pytest.fixture
def entry_point_objects()
eps = pkg_resources.iter_entry_points('cloudformation.template')
return [ep.load() for ep in eps]
#pytest.mark.parametrize('obj', entry_point_objects())
def test_generate_all(obj):
obj().build().to_json()

How to evade non-called fixture in Pytest?

Pytest suite has a brilliant feature of fixtures.
To make a reusable fixture, we mark a function with special decorator:
#pytest.fixture
def fix():
return {...}
It can later be used in our test through an argument name matching the original name of the fixture:
def test_me(fix):
fix['field'] = 'expected'
assert(fix['field'] == 'expected')
Although from time to time we might forget to specify the fixture in the arguments, and, since the name of the factory matches the name of the object produced, the test will silently apply changes to the factory object itself:
def test_me(): # notice no arg
fix['this is'] = 'a hell to debug'
Certainly, the outcome is undesirable. It would be nice, for instance, to be able to add some suffix to factory function, but the pytest.fixture decorator apparently does not have a means to override the name for the fixture.
Any other advice would suffice as well.
What is a recommended technique to protect ourselves from this kind of issue?
You can use autouse=True while defining fixture to invoke the fixture every time the scope of fixture starts. This feature was added in pytest 2.0.
For example:
import pytest
#pytest.fixture(scope='function',autouse=True)
def fixture_a():
return 5
def test_a():
assert fixture_a == 5
As you can see, I did not have to declare fixture as an argument in test_a to access it.
Documentation: https://docs.pytest.org/en/latest/reference.html#pytest-fixture
Code example: https://docs.pytest.org/en/latest/fixture.html#autouse-fixtures-xunit-setup-on-steroids

pytest fixture is always returning a function

I want to be able to return a value from a fixture to multiple tests/test classes, but the value that gets passed is a function.
Here's my code:
import pytest
#pytest.fixture()
def user_setup():
user = {
'name': 'chad',
'id': 1
}
return user
#pytest.mark.usefixtures('user_setup')
class TestThings:
def test_user(self):
assert user_setup['name'] == 'chad'
The output is:
=================================== FAILURES ===================================
_____________________________ TestThings.test_user _____________________________
self = <tests.test_again.TestThings instance at 0x10aed6998>
def test_user(self):
> assert user_setup['name'] == 'chad'
E TypeError: 'function' object has no attribute '__getitem__'
tests/test_again.py:14: TypeError
=========================== 1 failed in 0.02 seconds ===========================
But if I rewrite my test so that it doesn't use the usefixtures decorator, it works as expected:
def test_user(user_setup):
assert user_setup['name'] == 'chad'
Any ideas why it's not working when I try to use the decorator method?
When you use the #pytest.mark.usefixtures marker you still need to provide a similarly named input argument if you want that fixture to be injected in to your test function.
As described in the py.test docs for fixtures:
The name of the fixture function can later be referenced to cause its
invocation ahead of running tests... Test functions can directly use
fixture names as input arguments in which case the fixture instance
returned from the fixture function will be injected.
So just using the #pytest.mark.usefixtures decorator will only invoke the function. Providing an input argument will give you the result of that function.
You only really need to use #pytest.mark.usefixtures when you want to invoke a fixture but don't want to have it as an input argument to your test. As described in the py.test docs.
The reason you are getting an exception that talks about user_setup being a function is because inside your test_user function the name user_setup actually refers to the function you defined earlier in the file. To get your code to work as you expect you would need to add an argument to the test_user function:
#pytest.mark.usefixtures('user_setup')
class TestThings:
def test_user(self, user_setup):
assert user_setup['name'] == 'chad'
Now from the perspective of the test_user function the name user_setup will refer to the function argument which will be the returned value of the fixture as injected by py.test.
But really you just don't need to use the #pytest.mark.usefixtures decorator at all.
In both cases, in the global scope user_setup refers to the function. The difference is, in your nonfixture version, you are creating a parameter with the same name, which is a classic recipe for confusion.
In that nonfixture version, within in the scope of test_user, your user_setup identifier refers to whatever it is you are passing it, NOT the function in the global scope.
I think you probably mean to be calling user_setup and subscripting the result like
assert user_setup()['name'] == 'chad'

Categories