5454from _pytest .config import ExitCode
5555from _pytest .config .argparsing import Parser
5656from _pytest .deprecated import check_ispytest
57+ from _pytest .deprecated import FIXTURE_GETFIXTUREVALUE_DURING_TEARDOWN
5758from _pytest .deprecated import YIELD_FIXTURE
5859from _pytest .main import Session
5960from _pytest .mark import ParameterSet
@@ -506,6 +507,16 @@ def raiseerror(self, msg: str | None) -> NoReturn:
506507 """
507508 raise FixtureLookupError (None , self , msg )
508509
510+ def _raise_teardown_lookup_error (self , argname : str ) -> NoReturn :
511+ msg = (
512+ f'The fixture value for "{ argname } " is not available during teardown '
513+ "because it was not previously requested.\n "
514+ "Only fixtures that were already active can be retrieved during teardown.\n "
515+ "Request the fixture before teardown begins by declaring it in the fixture "
516+ "signature or by calling request.getfixturevalue() before the fixture yields."
517+ )
518+ raise FixtureLookupError (argname , self , msg )
519+
509520 def getfixturevalue (self , argname : str ) -> Any :
510521 """Dynamically run a named fixture function.
511522
@@ -515,8 +526,12 @@ def getfixturevalue(self, argname: str) -> Any:
515526 or test function body.
516527
517528 This method can be used during the test setup phase or the test run
518- phase, but during the test teardown phase a fixture's value may not
519- be available.
529+ phase. Avoid using it during the teardown phase.
530+
531+ .. versionchanged:: 9.1
532+ Calling ``request.getfixturevalue()`` during teardown to request a
533+ fixture that was not already requested
534+ :ref:`is deprecated <dynamic-fixture-request-during-teardown>`.
520535
521536 :param argname:
522537 The fixture name.
@@ -613,6 +628,16 @@ def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]:
613628 self , scope , param , param_index , fixturedef , _ispytest = True
614629 )
615630
631+ if not self .session ._setupstate .is_node_active (self .node ):
632+ # TODO(pytest10.1): Remove the `warn` and `if` and call
633+ # _raise_teardown_lookup_error unconditionally.
634+ warnings .warn (
635+ FIXTURE_GETFIXTUREVALUE_DURING_TEARDOWN .format (argname = argname ),
636+ stacklevel = 3 ,
637+ )
638+ if subrequest .node not in self .session ._setupstate .stack :
639+ self ._raise_teardown_lookup_error (argname )
640+
616641 # Make sure the fixture value is cached, running it if it isn't
617642 fixturedef .execute (request = subrequest )
618643
@@ -805,16 +830,7 @@ def formatrepr(self) -> FixtureLookupErrorRepr:
805830 stack = [self .request ._pyfuncitem .obj ]
806831 stack .extend (map (lambda x : x .func , self .fixturestack ))
807832 msg = self .msg
808- # This function currently makes an assumption that a non-None msg means we
809- # have a non-empty `self.fixturestack`. This is currently true, but if
810- # somebody at some point want to extend the use of FixtureLookupError to
811- # new cases it might break.
812- # Add the assert to make it clearer to developer that this will fail, otherwise
813- # it crashes because `fspath` does not get set due to `stack` being empty.
814- assert self .msg is None or self .fixturestack , (
815- "formatrepr assumptions broken, rewrite it to handle it"
816- )
817- if msg is not None :
833+ if msg is not None and len (stack ) > 1 :
818834 # The last fixture raise an error, let's present
819835 # it at the requesting side.
820836 stack = stack [:- 1 ]
0 commit comments