Reputation: 503
I have a fixture that is returning an object of certain type and I have another fixture defined in another file that basically uses the object to do other things. But I am not able to return the object from my first fixture.
file-1
def fixture_1(s, **kwargs):
def hook(s, **kwargs):
p_b = s.get()
p = p_b.build()
yield p
return hook
file-2
conftest.py
@pytest.fixture(scope='module')
def fixture_1(s, **kwargs):
def hook(s, **kwargs):
#Default implementation is no-op.
pass
return hook
@pytest.fixture(scope='module')
def fixture_2(s,b_p):
some_p = fixture_1(s)
current_status = s.start(some_p)
print(current_status)
yield current_status
I want to basically retrieve object p
returned in file-1
fixture_1
and use it in file-2
fixture_2
fixture.
Upvotes: 23
Views: 33301
Reputation: 8008
Easy example included
Py.test supports fixture invoking other fixtures straight out of the box
Have the fixtures in conftest.py or in test file:
conftest.py:
@pytest.fixture(scope="session")
def fixture_A():
some_obj = create_obj()
return some_obj # or use yield some_obj in case you want to destruct*
@pytest.fixture(scope="session")
def fixture_B(fixture_A):
this_is_some_obj = fixture_A
# do something
another = {}
return this_is_some_obj, another
test_example.py:
@pytest.fixture(scope="session")
def fixture_C(fixture_B):
return fixtureB
def test_finally_the_test(fixture_C):
some_obj, another = fixture_C
Important to mention that the fixtures above will be invoked once (even if multiple tests uses these fixtures) - this is due to the "session"
scope of each fixture
it is just as if these fixtures were singletons (if compared to OOP)
(For more pytest scopes)
Another note pytest knows the order to run the fixtures (it checks the dependencies - so nothing special to do here)
(More info about Destructing using yield)
Upvotes: 11
Reputation: 12158
I encountered this error because my scope was too high.
TLDR: Verify that your scopes are correct, and if needed, set the child to a lower scope – or create a new fixture from the parent in the child scope. scope="module"
was the culprit for me. I set to a lower scope - "function"
and it worked.
Here's what I was trying to do (oversimplified):
1. Test a class which uses pytest's temporary directory:
import dataclasses
@dataclasses.dataclass
class Storage:
directory: str
def test_my_class(tmpdir):
"""Ensure Storage object can be instantiated"""
s = Storage(directory=tmpdir)
assert s.directory == str(tmpdir)
💡 However, I was writing dozens of tests, and I did not want to instantiate dozens of storage class objects.
2. Instead, I created this fixture:
@pytest.fixture()
def my_storage(tmpdir) -> Storage:
return Storage(directory=tmpdir)
def test_my_class(my_storage, tmpdir):
"""Ensure Storage object can be instantiated"""
assert my_storage.directory == tmpdir
💡 But I wanted the scope to be on the module level.
3. Setting scope to module level causes error:
@pytest.fixture(scope="module")
def my_storage(tmpdir) -> Storage:
return Storage(directory=tmpdir)
ScopeMismatch: You tried to access the function scoped fixture tmpdir with a module scoped request object, involved factories:
4. Easy solution = Set scope to function (default):
@pytest.fixture(scope="function")
def my_storage(tmpdir) -> Storage:
return Storage(directory=tmpdir)
This works. But what if I needed the fixture on the module level?
5. Hard solution = Create a temporary directory as a fixture, in the designated scope:
@pytest.fixture(scope="module")
def my_tmpdir(tmpdir_factory):
return tmpdir_factory.mktemp("data")
@pytest.fixture(scope="module")
def my_storage(my_tmpdir) -> Storage:
return Storage(directory=my_tmpdir)
def test_my_class(my_storage, my_tmpdir):
"""Ensure Storage object can be instantiated"""
assert my_storage.directory == my_tmpdir
Upvotes: 0
Reputation: 1
For example, test()
can call fixture_2()
which can call fixture_1()
as shown below:
import pytest
@pytest.fixture
def fixture_1():
return "fixture_1"
@pytest.fixture
def fixture_2(fixture_1):
return fixture_1
def test(fixture_2):
print(fixture_2)
assert True
Output:
$ pytest -q -rP
. [100%]
=============== PASSES ================
________________ test _________________
-------- Captured stdout call ---------
fixture_1
1 passed in 0.10s
Upvotes: 2
Reputation: 2429
It seems that you are using pytest fixtures wrong (looking at your arguments names).
I'd recommend you to go through https://docs.pytest.org/en/latest/fixture.html
There are two solutions for your problem:
###
# file_1
def not_really_a_fixture(s, **kwargs): # just some hook generator
def hook(s, **kwargs):
p_b = s.get()
p = p_b.build()
yield p
return hook
###
# conftest.py
from file_1 import not_really_a_fixture
@pytest.fixture(scope='module')
def fixture_2(s,b_p): # s and b_p should be names of fixtures that need to run before this
some_p = not_really_a_fixture(s)
current_status = s.start(some_p)
print(current_status)
yield current_status
And:
###
# file_1
@pytest.fixture(scope='module')
def fixture_1(s): # s is name of another fixture
# there is no point in having **kwargs as arg in pytest fixture
def hook(s, **kwargs):
#Default implementation is no-op.
pass
return hook
###
# conftest.py
from file_1 import fixture_1
@pytest.fixture(scope='module')
def fixture_2(s,b_p,fixture_1): # s and b_p should be names of fixtures that need to run before this
# in fixture_1 is value returned by fixture_1, that means your hook func
current_status = s.start(fixture_1)
print(current_status)
yield current_status
Upvotes: 7