diff --git a/changelog/10819.bugfix.rst b/changelog/10819.bugfix.rst new file mode 100644 index 00000000000..0d3cc380de9 --- /dev/null +++ b/changelog/10819.bugfix.rst @@ -0,0 +1,3 @@ +Fixed class-scoped fixtures defined in base classes not binding to the correct test instance when inherited by child test classes -- by :user:`yastcher`. + +Fixes :issue:`10819` and :issue:`14011`. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index d8d19fcac6d..1cea0236e94 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1148,6 +1148,10 @@ def resolve_fixture_function( # request.instance so that code working with "fixturedef" behaves # as expected. instance = request.instance + + if instance is None and fixturedef._scope is Scope.Class: + instance = getattr(request._pyfuncitem, "instance", None) + if instance is not None: # Handle the case where fixture is defined not in a test class, but some other class # (for example a plugin class with a fixture), see #2270. diff --git a/testing/test_inherit_class_fixture.py b/testing/test_inherit_class_fixture.py new file mode 100644 index 00000000000..c4838e147a8 --- /dev/null +++ b/testing/test_inherit_class_fixture.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +import typing + +import pytest + + +class ParentBase: + """from issue #14011""" + + name = "" + variable = "" + + def setup(self) -> None: + self.variable = self.name + + def teardown(self) -> None: + pass + + @pytest.fixture(scope="class") + def fix(self) -> typing.Generator[None]: + self.setup() + yield + self.teardown() + + @pytest.fixture(scope="class", autouse=True) + def base_autouse(self) -> None: + self.flag = True + + +@pytest.mark.usefixtures("fix") +class Test1(ParentBase): + name = "test1" + + def test_a(self) -> None: + assert self.variable == self.name + + +@pytest.mark.usefixtures("fix") +class Test2(ParentBase): + name = "test2" + + def test_a(self) -> None: + assert self.variable == self.name + + +class TestChild(ParentBase): + def test_flag(self) -> None: + assert self.flag + + +class BaseTestClass: + """from issue #10819""" + + test_func_scope_set = None + test_class_scope_set = None + + @pytest.fixture(scope="class", autouse=True) + def dummy_class_fixture(self) -> None: + self.test_class_scope_set = True + + @pytest.fixture(scope="function", autouse=True) + def dummy_func_fixture(self) -> None: + self.test_func_scope_set = True + + +class TestDummy(BaseTestClass): + def test_dummy(self) -> None: + assert self.test_func_scope_set is True + assert self.test_class_scope_set is True