mark.usefixtures only seems to work on tests directly, not within other fixtures, which is I assume part of whats going on here.
For this case, you could fall back to request.getfixturevalue
Dynamically run a named fixture function. Declaring fixtures via function argument is recommended where possible. But if you can only decide whether to use another fixture at test setup time, you may use this function to retrieve it inside a fixture or test function body.
@pytest.fixture
def bar_fixture():
do_something_but_Return_no_value()
@pytest.mark.parametrize(
["foo", "fixtures"],
[
pytest.param(
True, [], marks=[pytest.mark.test_1],
),
pytest.param(
False, ["bar_fixture"], marks=[pytest.mark.test_2]
),
],
)
def test_foo(request, foo, fixtures):
for fixture in fixtures:
request.getfixturevalue(fixture)
func(foo)
This obviously isn't as elegant as other options, but its easy enough to follow.
Answer from flakes on Stack OverflowVideos
Use the usefixtures marker:
# conftest.py
import pytest
@pytest.fixture
def mocks(mocker):
mocker.patch('os.path.isdir', return_value=True)
# test_me.py
import os
import pytest
@pytest.mark.usefixtures('mocks')
def test_mocks():
assert os.path.isdir('/this/is/definitely/not/a/dir')
Passing multiple fixtures is also posible:
@pytest.mark.usefixtures('mocks', 'my_other_fixture', 'etc')
However, there is a caveat: the mocks fixture in your code returns a value ret_val. When passing the fixture via test function args, this value is returned under the mocks arg; when you use markers, you don't have the arg anymore, so you won't be able to use the value. Should you be needing the mock value, pass the fixture in the test function args. There are some other ways imaginable, like passing ret_val via cache, however, the resulting code will be more complicated and less readable so I wouldn't bother.
I was able to implement this by using the pytest_collection_modifyitems hook to adjust the list of fixtures on the test item after collection:
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(items):
'''
Check if any tests are marked to use the mock.
'''
for item in items:
if item.get_closest_marker('mocks'):
item.fixturenames.append('mocks')
Adjusting the Item.fixturenames list after collection appears to trigger the fixture setup in the way I was hoping.
If you don't care about using a custom mark, however, @hoefling's recommendation to use the built-in usefixtures mark would be a good solution, too.
Both Pytest Fixtures and regular functions can be used to structure to test code and reduce code duplication. The answer provided by George Udosen does an excellent job explaining that.
However, the OP specifically asked about the differences between a pytest.fixture and a regular Python function and there is a number of differences:
Pytest Fixtures are scoped
By default, a pytest.fixture is executed for each test function referencing the fixture. In some cases, though, the fixture setup may be computationally expensive or time consuming, such as initializing a database. For that purpose, a pytest.fixture can be configured with a larger scope. This allows the pytest.fixture to be reused across tests in a module (module scope) or even across all tests of a pytest run (session scope). The following example uses a module-scoped fixture to speed up the tests:
from time import sleep
import pytest
@pytest.fixture(scope="module")
def expensive_setup():
return sleep(10)
def test_a(expensive_setup):
pass # expensive_setup is instantiated for this test
def test_b(expensive_setup):
pass # Reuses expensive_setup, no need to wait 10s
Although different scoping can be achieved with regular function calls, scoped fixtures are much more pleasant to use.
Pytest Fixtures are based on dependency injection
Pytest registers all fixtures during the test collection phase. When a test function requires an argument whose name matches a registered fixture name, Pytest will take care that the fixture is instantiated for the test and provide the instance to the test function. This is a form of dependency injection.
The advantage over regular functions is that you can refer to any pytest.fixture by name without having to explicitly import it. For example, Pytest comes with a tmp_path fixture that can be used by any test to work with a temporary file. The following example is taken from the Pytest documentation:
CONTENT = "content"
def test_create_file(tmp_path):
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.txt"
p.write_text(CONTENT)
assert p.read_text() == CONTENT
assert len(list(tmp_path.iterdir())) == 1
assert 0
The fact that users don't have to import tmp_path before using it is very convenient.
It is even possible to apply a fixture to a test function without the test function requesting it (see Autouse fixtures).
Pytest fixtures can be parametrized
Much like test parametrization, fixture parametrization allows the user to specify multiple "variants" of a fixture, each with a different return value. Every test using that fixture will be executed multiple times, once for each variant. Say you want to test that all your code is tested for HTTP as well as HTTPS URLs, you might do something like this:
import pytest
@pytest.fixture(params=["http", "https"])
def url_scheme(request):
return request.param
def test_get_call_succeeds(url_scheme):
# Make some assertions
assert True
The parametrized fixture will cause each referencing test to be executed with each version of the fixture:
$ pytest
tests/test_fixture_param.py::test_get_call_succeeds[http] PASSED [ 50%]
tests/test_fixture_param.py::test_get_call_succeeds[https] PASSED [100%]
======== 2 passed in 0.01s ========
Conclusion
Pytest fixtures provide many quality-of-life improvements over regular function calls. I recommend to always prefer Pytest fixtures over regular functions, unless you must be able to call the fixture directly. Directly invoking pytest fixtures is not indended and the call will fail.
A fixture is used:
to have a specific function to be used many times and to simplify a test.
to do something before stating and after finishing test with yield.
For example, there are test_get_username() and test_set_check_password() in Django as shown below:
import pytest
from django.contrib.auth.models import User
@pytest.mark.django_db
def test_get_username():
user = User.objects.create_user("test-user")
user.username = "John"
assert user.get_username() == "John"
@pytest.mark.django_db
def test_set_password_and_check_password():
user = User.objects.create_user("test-user")
user.set_password("new-password")
assert user.check_password("new-password") == True
Then, you can create and use user() with @pytest.fixture to use it many times and to simplify test_get_username() and test_set_password_and_check_password() as shown below:
import pytest
from django.contrib.auth.models import User
@pytest.fixture # Here
def user():
user = User.objects.create_user("test-user")
return user
@pytest.mark.django_db
def test_get_username(user): # <- user
user.username = "John"
assert user.get_username() == "John"
@pytest.mark.django_db
def test_set_password_and_check_password(user): # <- user
user.set_password("new-password")
assert user.check_password("new-password") == True
And, for example, there are fixture_1() with yield and test_1() as shown below:
import pytest
@pytest.fixture
def fixture_1():
print('Before Test')
yield 6
print('After Test')
def test_1(fixture_1):
print('Running Test')
assert fixture_1 == 6
Then, this is the output below:
$ pytest -q -rP
. [100%]
=============== PASSES ===============
_______________ test_1 _______________
------- Captured stdout setup --------
Before Test
-------- Captured stdout call --------
Running Test
------ Captured stdout teardown ------
After Test
1 passed in 0.10s
In addition, you can run multiple fixtures for test_1() and test_2() as shown below. *My answer explains how to call a fixture from another fixture in Pytest and my answer explains how to pass parameters or arguments to a fixture in Pytest and my answer explains how to use a fixture with @pytest.mark.parametrize() and my answer explains how to use fixtures as arguments in @pytest.mark.parametrize():
import pytest
@pytest.fixture
def fixture_1():
return "fixture_1"
@pytest.fixture
def fixture_2():
return "fixture_2"
def test_1(fixture_1, fixture_2):
print(fixture_1, fixture_2)
assert True
def test_2(fixture_1, fixture_2):
print(fixture_1, fixture_2)
assert True
Then, this is the output below:
$ pytest -q -rP
.. [100%]
=============== PASSES ================
_______________ test_1 ________________
-------- Captured stdout call ---------
fixture_1 fixture_2
_______________ test_2 ________________
-------- Captured stdout call ---------
fixture_1 fixture_2
2 passed in 0.33s
And, you can use request.getfixturevalue() to run fixture_1() and fixture_2() in test_1() and test_2() as shown below:
import pytest
@pytest.fixture
def fixture_1():
return "fixture_1"
@pytest.fixture
def fixture_2():
return "fixture_2"
def test_1(request):
print(request.getfixturevalue("fixture_1"))
print(request.getfixturevalue("fixture_2"))
assert True
def test_2(request):
print(request.getfixturevalue("fixture_1"))
print(request.getfixturevalue("fixture_2"))
assert True
Then, this is the output below:
$ pytest -q -rP
.. [100%]
=============== PASSES ================
_______________ test_1 ________________
-------- Captured stdout call ---------
fixture_1
fixture_2
_______________ test_2 ________________
-------- Captured stdout call ---------
fixture_1
fixture_2
2 passed in 0.10s
And, you can use @pytest.fixture(autouse=True) to run fixtures for all the tests test_1() and test_2() without setting fixtures as the tests' parameters as shown below. *A test cannot use fixtures' return values without fixtures' parameters:
import pytest
@pytest.fixture(autouse=True)
def fixture_1():
print("fixture_1")
@pytest.fixture(autouse=True)
def fixture_2():
print("fixture_2")
def test_1():
assert True
def test_2():
assert True
Then, this is the output below:
$ pytest -q -rP
.. [100%]
=============== PASSES ================
_______________ test_1 ________________
-------- Captured stdout setup --------
fixture_1
fixture_2
_______________ test_2 ________________
-------- Captured stdout setup --------
fixture_1
fixture_2
2 passed in 0.33s
And, you can use @pytest.mark.usefixtures for test_1() and test_2() to run multiple fixtures as shown below. *A test cannot use fixtures' return values without fixtures' parameters:
import pytest
@pytest.fixture
def fixture_1():
print("fixture_1")
@pytest.fixture
def fixture_2():
print("fixture_2")
@pytest.mark.usefixtures('fixture_1', 'fixture_2')
def test_1():
assert True
@pytest.mark.usefixtures('fixture_1', 'fixture_2')
def test_2():
assert True
Then, this is the output below:
$ pytest -q -rP
.. [100%]
=============== PASSES ================
_______________ test_1 ________________
-------- Captured stdout setup --------
fixture_1
fixture_2
_______________ test_2 ________________
-------- Captured stdout setup --------
fixture_1
fixture_2
2 passed in 0.33s
And, you can use @pytest.mark.usefixtures for test_1() and test_2() in Test class to run multiple fixtures as shown below. *A fixture can use request.cls.something to pass data to the tests in a class:
import pytest
@pytest.fixture
def fixture_1(request):
request.cls.first_name = "John"
@pytest.fixture
def fixture_2(request):
request.cls.last_name = "Smith"
@pytest.mark.usefixtures('fixture_1', 'fixture_2')
class Test:
def test_1(self):
print(self.first_name, self.last_name)
assert True
def test_2(self):
print(self.first_name, self.last_name)
assert True
Then, this is the output below:
$ pytest -q -rP
.. [100%]
=============== PASSES ================
_____________ Test.test_1 _____________
-------- Captured stdout call ---------
John Smith
_____________ Test.test_2 _____________
-------- Captured stdout call ---------
John Smith
2 passed in 0.32s
And, you can use @pytest.fixture(autouse=True) to run fixtures for all the tests test_1() and test_2() in Test class without setting fixtures as the tests' parameters as shown below. *A fixture can use request.cls.something to pass data to the tests in a class:
import pytest
@pytest.fixture
def fixture_1(request):
request.cls.first_name = "John"
@pytest.fixture
def fixture_2(request):
request.cls.last_name = "Smith"
@pytest.mark.usefixtures('fixture_1', 'fixture_2')
class Test:
def test_1(self):
print(self.first_name, self.last_name)
assert True
def test_2(self):
print(self.first_name, self.last_name)
assert True
Then, this is the output below:
$ pytest -q -rP
.. [100%]
=============== PASSES ================
_____________ Test.test_1 _____________
-------- Captured stdout call ---------
John Smith
_____________ Test.test_2 _____________
-------- Captured stdout call ---------
John Smith
2 passed in 0.33s