Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using fixtures in pytest.mark.parametrize #349

Open
pytestbot opened this issue Aug 30, 2013 · 116 comments
Open

Using fixtures in pytest.mark.parametrize #349

pytestbot opened this issue Aug 30, 2013 · 116 comments
Labels
topic: parametrize related to @pytest.mark.parametrize type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature

Comments

@pytestbot
Copy link
Contributor

Originally reported by: Florian Rathgeber (BitBucket: frathgeber, GitHub: frathgeber)


I often have a use case like the following contrived example:

@pytest.fixture
def a():
    return 'a'

@pytest.fixture
def b():
    return 'b'

@pytest.mark.parametrize('arg', [a, b])
def test_foo(arg):
    assert len(arg) == 1

This doesn't currently do what's intended i.e. arg inside the test function is not the fixture value but the fixture function.

I can work around it by introducing a "meta fixture", but that's rather ugly:

@pytest.fixture(params=['a', 'b'])
def arg(request, a, b):
    return {'a': a, 'b': b}[request.param]

def test_foo(arg):
    assert len(arg) == 1

It would be convenient if a syntax like in the first case was supported.


@pytestbot
Copy link
Contributor Author

Original comment by Matthias Geier (BitBucket: geier, GitHub: geier):


This would be a great feature!

I found another (I don't know if more or less ugly) work-around:

#!python

@pytest.mark.parametrize('arg', ['a', 'b'])
def test_foo(arg, request):
    val = request.getfuncargvalue(arg)
    assert len(val) == 1

This doesn't work, however, with parametrized fixtures.

BTW, it would also be great if fixtures were supported in the params argument of pytest.fixture.

@pytestbot
Copy link
Contributor Author

Original comment by Floris Bruynooghe (BitBucket: flub, GitHub: flub):


Tentatively assigning this to me as I think I might be able to come up with a reasonable patch. It'll probably take me a long while though so don't let that discourage anyone else from working on this, assigning it more as a way of not forgetting about it.

@pytestbot
Copy link
Contributor Author

Original comment by Praveen Shirali (BitBucket: praveenshirali, GitHub: praveenshirali):


The quoted examples work because functions a and b are part of the same module as test_foo, and within the scope of the example, the parametrization should work even if @pytest.fixture decorator isn't present around functions a and b. They are getting used as regular python functions and not as pytest fixtures. Note that fixtures can also be defined in external modules like conftest.py.

Another alternative to the above example is to directly call these functions in the list.

#!python

@pytest.mark.parametrize('arg', [a(), b()])
def test_foo(arg):
    assert len(arg) == 1

@pytestbot
Copy link
Contributor Author

Original comment by BitBucket: dpwrussell, GitHub: dpwrussell:


This would be an awesome feature.

@praveenshirali I don't think your alternative is used as a fixture, it just calls the fixture function. So it would be run repeatedly. You would also have to specify the arguments to the fixture if there were any which could begin the cycle over again if they are also fixtures.

@pytestbot pytestbot added the type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature label Jun 15, 2015
@mgeier
Copy link

mgeier commented Jun 16, 2015

@pfctdayelise pfctdayelise added the topic: parametrize related to @pytest.mark.parametrize label Jul 25, 2015
@jlmenut
Copy link

jlmenut commented Nov 19, 2015

Yes, I would like very much to have this feature also. Maybe a line in the doc explaining it's not possible for the moment would be useful also.

@kevincox
Copy link
Contributor

It would also be killer if this supported parameterized fixtures generating the product of the fixtures. Although this might be a little much.

@pytest.fixture(params=["1", " ", 1, True, [None], {1:2}])
def truthy(request):
    return request.param

@pytest.fixture(params=[False, None, "", 0, [], {}])
def falsey(request):
    return request.param

@pytest.mark.parameterize("val,res", [
    (truthy, True),
    (falsey, False),
])
def test_bool(val, res)
    assert bool(val) is res

@SUNx2YCH
Copy link

+1 for this feature.
BTW, combining Florian Rathgeber and Matthias Geier solutions we can get a bit nicer "meta fixture":

@pytest.fixture
def a():
    return 'a'

@pytest.fixture
def b():
    return 'b'

@pytest.fixture(params=['a', 'b'])
def arg(request):
    return request.getfuncargvalue(request.param)

def test_foo(arg):
    assert len(arg) == 1

@rabbbit
Copy link

rabbbit commented Jul 4, 2016

+1 on this.

I'm currently writing tests that look like:

@pytest.fixture
def my_fixture():
      return 'something'

@pytest.fixture
def another_fixture(my_fixture):
      return {'my_key': my_fixture}

def yet_another_fixture():
     return {'my_key': None}

@pytest.mark.parametrize('arg1, arg2', [
    (5, another_fixture(my_fixture())),
    (5, yet_another_fixture()),
)
def my_test(arg1, arg2):
    assert function_under_test(arg2) == arg1

and that's rather ugly.

@RonnyPfannschmidt
Copy link
Member

@rabbbit your example is structurally wrong and runs fixture code at test importation time

@rabbbit
Copy link

rabbbit commented Jul 5, 2016

@RonnyPfannschmidt I know - and that's why I'd like to be able to use fixtures in parametrize? And that would be awesome.

My example is wrong, but it follows the guideline of "always use fixtures". Otherwise we'd end up with fixtures in normal tests, and workarounds in parametrized tests.

Or is there a way of achieving this already, outside of dropping parametrize and doing 'if/elses' in the test function?

@RonnyPfannschmidt
Copy link
Member

There is a upcoming proposal wrt "merged" fixtures, there is no implementation yet

@nicoddemus
Copy link
Member

For reference: #1660

@rabbbit
Copy link

rabbbit commented Jul 5, 2016

ok, I don't understand python.

If the below works:

@pytest.fixture
def my_fixture
    return 1

def test_me(my_fixture):
    assert 1 == my_fixture

wouldn't the below be simpler? And an exact equivalent?

@pytest.fixture
def my_fixture
    return 1

@pytest.mark.parametrize('fixture', [my_fixture])
def test_me(fixture):
    assert 1 == my_fixture

Am I wrong to think that mark.parametrize could figure out whether an argument is a pytest.fixture or not?

@RonnyPfannschmidt
Copy link
Member

atm parametrize cannot figure it, and it shouldnt figure it
there will be a new object to declare a parameter will request a fixture/fixture with parameters

some documentation for that is in the features branch

@rabbbit
Copy link

rabbbit commented Jul 5, 2016

yeah, I read the proposal.

I'm just surprised you're going with pytest.fixture_request(' default_context')it feels very verbose?

after all,

@pytest.fixture
def my_fixture
    return 1

def test_me(my_fixture):
    assert 1 == my_fixture

could also turn to

@pytest.fixture
def my_fixture
    return 1

def test_me(pytest.fixture_request(my_fixture)):
    assert 1 == my_fixture

but that's not the plan, right?

On 5 July 2016 at 16:17, Ronny Pfannschmidt notifications@github.com
wrote:

atm parametrize cannot figure it, and it shouldnt figure it
there will be a new object to declare a parameter will request a
fixture/fixture with parameters

some documentation for that is in the features branch


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#349 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AARt0kUxVSL1GMeCtU-Kzy3Pg80mW7Ouks5qSmdmgaJpZM4FEMDj
.

@RonnyPfannschmidt
Copy link
Member

thats not even valid python syntax

the fixture-request would be used as a parameter value to tell py.test "use the value of a fixture you create later on"

there are already parametzw using examples, and its not overly verbose

@kibernick
Copy link

It would be really convenient to have this functionality, perhaps along the lines of "LazyFixture" in pytest-factoryboy

@RonnyPfannschmidt
Copy link
Member

@kibernick we already did put a outline of possible implementations into the documentation

we just need time or a person implementing it

@Brachi
Copy link

Brachi commented Sep 30, 2016

@RonnyPfannschmidt can you link to that part of the documentation you mention? Can't find it.
Edit: nevermind. http://doc.pytest.org/en/latest/proposals/parametrize_with_fixtures.html

@TvoroG
Copy link

TvoroG commented Oct 2, 2016

@RonnyPfannschmidt, can you please check this out https://github.com/TvoroG/pytest-fixture-mark? Need some feedback

@Brachi
Copy link

Brachi commented Oct 3, 2016

@TvoroG good work.
Currently this seems to be failing. Is it possible to support it as well?

import pytest                                                                  


@pytest.fixture(params=[1, 2, 3])                                              
def one(request):                                                              
    return str(request.param)                                                  


@pytest.fixture                                                                
def two():                                                                     
    return 4                                                                   


@pytest.fixture(params=[                                                       
    pytest.mark.fixture('one'),                                                
    pytest.mark.fixture('two')                                                 
])                                                                             
def some(request):                                                             
    return request.param                                                       


def test_func(some):                                                           
    assert some in {'1', '2', '3', 4}

@TvoroG
Copy link

TvoroG commented Oct 4, 2016

@Brachi, thanks for catching it! It works now, but more nested structures need some dependency sorting to instantiate fixtures in correct order. I'll update the plugin code when i'm done.

@TvoroG
Copy link

TvoroG commented Oct 4, 2016

@Brachi, I fixed it. Let me know if there is more such cases when plugin is failing

@Brachi
Copy link

Brachi commented Oct 4, 2016

@TvoroG great, thanks for the quick reply. I tested it a little more and here's another contrived example that doesn't work, based on a real use case (where one actually returns an object)

import pytest


@pytest.fixture(params=[1, 2, 3])
def one(request):
    return request.param


@pytest.fixture(params=[pytest.mark.fixture('one')])
def as_str(request):
    return str(request.getfixturevalue('one'))


@pytest.fixture(params=[pytest.mark.fixture('one')])
def as_hex(request):
    return hex(request.getfixturevalue('one'))


def test_as_str(as_str):
    assert as_str in {'1', '2', '3'}


def test_as_hex(as_hex):
    assert as_hex in {'0x1', '0x2', '0x3'}


# fails at setup time, with ValueError: duplicate 'one'
def test_as_hex_vs_as_str(as_str, as_hex):
    assert int(as_hex, 16) == int(as_str)

@nicoddemus
Copy link
Member

@TvoroG, pytest-fixture-mark seems very nice! I hope I get the chance to try it soon! 😄

Perhaps discussion specific to it should be moved to https://github.com/TvoroG/pytest-fixture-mark thought? 😉

@sleonardoaugusto
Copy link

sleonardoaugusto commented Apr 2, 2021

Usually I have a file with unittest assertions, so I mix it with pytest when I need it.
In this case, it's a Django project.

django_assertions.py

from django.test import TestCase

_dj_testcase = TestCase()

dj_sub_test = _dj_testcase.subTest

test_serializers.py

from django_assertions import dj_sub_test

@pytest.fixture
def attendance():
    attendance = baker.make('Attendance', _fill_optional=True)
    baker.make('AttendanceFile', attendance=attendance)
    return attendance

@pytest.mark.django_db
def test_values(attendance):
    serializer = AttendanceSerializer(attendance)
    values = (
        ('id', attendance.pk),
        ('customer_name', attendance.customer_name),
        ('document_id', attendance.document_id),
        ('status', attendance.status),
        ('status_label', AttendanceStatus(attendance.status).label),
        ('resume', attendance.resume),
    )
    for attr, value in values:
        with dj_sub_test():
            assert serializer.data[attr] == value

@remort
Copy link

remort commented Aug 10, 2021

Solved my task by using first (out of two) methods described here: https://miguendes.me/how-to-use-fixtures-as-arguments-in-pytestmarkparametrize

@smarie
Copy link
Contributor

smarie commented Aug 18, 2021

@remort , this works as long as your fixtures are not parametrized, otherwise you'll need pytest-cases as shown in this example:

from pytest_cases import fixture, parametrize

@fixture
@parametrize(a=[0, 1])
def my_fix(a):
    return f"my_fix_{a}"

@parametrize(b=["hello", my_fix])
def test_foo(b):
    print(b)

leading to

test_tmp.py::test_foo[hello] 
test_tmp.py::test_foo[my_fix-a=0] 
test_tmp.py::test_foo[my_fix-a=1] 

(note that I used the alternative kwargs syntax above but you can also use the pytest.mark.parametrize syntax if you prefer)

@waynew
Copy link

waynew commented Mar 18, 2022

For another concrete example, I'm currently working on a PR where I am trying to write a single test (suite) to run against several different backends. What I would like to be able to write is something like this:

@pytest.fixture(scope="module", params=get_mysql_versions())
def mysql_image(request):
    image = docker.pull(tag=request.param)
    yield image

@pytest.fixture(scope="module")
def mysql_container(mysql_image):
    # Maybe do some other stuff here
    container = docker.run(image=mysql_image) 
    yield container

@pytest.fixture
def mysql_cache(mysql_container):
    cache = MySqlCache(config=mysql_container.config)
    yield cache


# Do similar setup for other cache images, containers, and cache SUTs here

@pytest.mark.parametrize(
    "cache",
    [mysql_cache, consul_cache, etcd_cache, redis_cache, localfs_cache],
)
def test_cache(cache):
    result = cache.do_something('value')
    assert result == 'expected result'    

I'm currently working around it by using request.getfixturevalue, but that means that I'm gonna need a single fixture for every one of my versions 😞

Unless there's currently a better way to do this.

@RonnyPfannschmidt
Copy link
Member

Pytest-lazy-fixtures is a external plugin to do part of that

@smarie
Copy link
Contributor

smarie commented Mar 19, 2022

And pytest-cases works too @waynew :) See #349 (comment) and doc reference https://smarie.github.io/python-pytest-cases/pytest_goodies/#parametrize

EDIT: simply replace @pytest.mark.parametrize with @parametrize and @pytest.fixture with @fixture in your example and it should work, with both parametrize and fixture imported from pytest_cases.

@codectl

This comment was marked as off-topic.

@RonnyPfannschmidt

This comment was marked as off-topic.

@Lewiscowles1986

This comment was marked as off-topic.

@chrisk314
Copy link

Just a quick note to say the pytest-lazy-fixture package mentioned as a workaround here appears to be dead, with many unaddressed open issues and it's not compatible with pytest>=8.0.0. This would be a nice quality of life feature to have natively in pytest.

@smarie
Copy link
Contributor

smarie commented Jul 12, 2024

@chrisk314 You can use pytest-cases as a workaround (https://smarie.github.io/python-pytest-cases), it is alive and kicking :) (and compliant with pytest 8 since version 3.8.3)
See #349 (comment)

Still, I agree with you that it would be better to refactor the internal engine to handle all of this in pytest. This would require massive rework unfortunately... which is why the team did not do it yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: parametrize related to @pytest.mark.parametrize type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature
Projects
None yet
Development

No branches or pull requests