pytest
pytest copied to clipboard
conftest.py definitions in subdirectories shadow those in parent directories
When there is a conftest.py in a subdirectory that contains a pytest.fixture of the same name as one in a conftest.py file in a parent directory, the fixture in the subdirectory runs for tests in the subdirectory, but the one in the parent does not.
Repro
Create a directory. Put in:
conftest.py
conftest.py
import os
import pytest
@pytest.fixture(autouse=True)
def record_file_attribute(record_property, request):
file_path = os.path.relpath(request.node.location[0])
record_property("file", file_path)
test_hello.py
def test_hello(record_property):
assert True
submod/__init__.py (empty file)
submod/test_hello.py with the same contents as test_hello.py above.
submod/conftest.py with the same contents as test_hello.py above, but with "file" changed to "file2".
Run pytest in the root directory with --junitxml=output.xml. The output will be:
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite name="pytest" errors="0" failures="0" skipped="0" tests="2" time="0.028" timestamp="2023-03-30T11:32:48.799625" hostname="fross-mbp">
<testcase classname="test_minimal" name="test_hello" file="test_hello.py" line="0" time="0.001">
<properties>
<property name="file" value="test_hello.py"/>
</properties>
</testcase>
<testcase classname="submod.test_hello" name="test_other" file="submod/test_hello.py" line="0" time="0.001">
<properties>
<property name="file2" value="submod/test_hello.py"/>
</properties>
</testcase>
</testsuite>
</testsuites>
instead of having a file property on both.
pytest version 6.2.5
That is intentional
If you want the parent fixture to run, take it as a parameter
Should you at least provide a warning that it is being shadowed?
Introduction of a marker to indicate whether a fixture should be overridden seems like a good ida, there is a number of cases where its intentional
If you want the parent fixture to run, take it as a parameter
(you accidentally posted twice, I deleted the redundant comment)
Looks like even that doesn't always work. I didn't try with conftest.py files, but if you have two plugins, depending on plugin order, the "overriding" fixture will actually be loaded first, and then be shadowed by the to-be-overridden one. While the overriding one has an (unresolved) dependency on the to-be-overridden one, it's never actually called, so there is no failure.
Here is a reproducer:
import pytest
pytest_plugins = ["pytester"]
class Plugin1:
@pytest.fixture
def fixt(self):
pass
class Plugin2:
@pytest.fixture
def fixt(self, fixt):
1/0 # never gets called
def test_override(pytester):
pytester.makepyfile("""
def test_fixt(fixt):
pass
""")
res = pytester.runpytest("--setup-show", plugins=[Plugin2(), Plugin1()]) # change plugin order to make override work
print(res.stdout)
assert False
While here it's trivial to change the plugin order, for conftest.py files (or installed plugins), that's not going to work.
I had a few discussions here with @lovetheguitar about this, but we didn't really get anywhere. The gist of it is that overriding fixtures is both done accidentally (as in the OP or my comment above), as well as intentionally (e.g. many plugins let you override a fixture in your conftest.py to configure some aspect of it).
I like the idea that @RonnyPfannschmidt had mentioned above, and would imagine that as a @pytest.fixture(override=True), with pytest emitting a warning if an override happens without override=True being given.
Open questions:
- Should we really do this for any overriding of fixtures, no matter the definition location, even if e.g. a fixture in a class overrides one in a module? Or only e.g. if a fixture in a plugin overrides another fixture that's in a different plugin (that one is pretty clearly a mistake, and indeterministic depending on plugin load order)?
- Should we still do this when someone does
def fixt(fixt):where it's quite clear we're explicitly overriding and extendingfixt, rather than just shadowing it?
this has some special edge cases - some fixutres are intended to be replaced (like the webdriver ones for selenium, others are not, but may or may not end up overtwritten depending on known plugins