pyzzled
pyzzled

Reputation: 731

How to 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?

Upvotes: 73

Views: 52382

Answers (5)

For example, you can pass 2 arguments to addition() fixture which has the nested function core() as shown below:

import pytest

@pytest.fixture
def addition():
    def core(num1, num2):
        return num1 + num2
    return core

def test(request, addition):
    print(addition(2, 3))
    print(request.getfixturevalue("addition")(6, 8))
    assert True

Output:

$ pytest -q -rP
.                                [100%]
=============== PASSES ================ 
________________ test _________________ 
-------- Captured stdout call --------- 
5
14
1 passed in 0.10s

Upvotes: 0

Carlos Pascual
Carlos Pascual

Reputation: 141

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

Upvotes: 14

nitzpo
nitzpo

Reputation: 517

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)

Upvotes: 19

supamaze
supamaze

Reputation: 9473

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

Upvotes: 125

Alfe
Alfe

Reputation: 59416

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.

Upvotes: 9

Related Questions