1515from typing import Union
1616
1717import _pytest ._code
18- from _pytest .compat import getimfunc
1918from _pytest .compat import is_async_function
2019from _pytest .config import hookimpl
2120from _pytest .fixtures import FixtureRequest
@@ -63,6 +62,14 @@ class UnitTestCase(Class):
6362 # to declare that our children do not support funcargs.
6463 nofuncargs = True
6564
65+ def newinstance (self ):
66+ # TestCase __init__ takes the method (test) name. The TestCase
67+ # constructor treats the name "runTest" as a special no-op, so it can be
68+ # used when a dummy instance is needed. While unittest.TestCase has a
69+ # default, some subclasses omit the default (#9610), so always supply
70+ # it.
71+ return self .obj ("runTest" )
72+
6673 def collect (self ) -> Iterable [Union [Item , Collector ]]:
6774 from unittest import TestLoader
6875
@@ -76,15 +83,15 @@ def collect(self) -> Iterable[Union[Item, Collector]]:
7683 self ._register_unittest_setup_class_fixture (cls )
7784 self ._register_setup_class_fixture ()
7885
79- self .session ._fixturemanager .parsefactories (self , unittest = True )
86+ self .session ._fixturemanager .parsefactories (self .newinstance (), self .nodeid )
87+
8088 loader = TestLoader ()
8189 foundsomething = False
8290 for name in loader .getTestCaseNames (self .obj ):
8391 x = getattr (self .obj , name )
8492 if not getattr (x , "__test__" , True ):
8593 continue
86- funcobj = getimfunc (x )
87- yield TestCaseFunction .from_parent (self , name = name , callobj = funcobj )
94+ yield TestCaseFunction .from_parent (self , name = name )
8895 foundsomething = True
8996
9097 if not foundsomething :
@@ -169,31 +176,28 @@ def unittest_setup_method_fixture(
169176class TestCaseFunction (Function ):
170177 nofuncargs = True
171178 _excinfo : Optional [List [_pytest ._code .ExceptionInfo [BaseException ]]] = None
172- _testcase : Optional ["unittest.TestCase" ] = None
173179
174180 def _getobj (self ):
175- assert self .parent is not None
176- # Unlike a regular Function in a Class, where `item.obj` returns
177- # a *bound* method (attached to an instance), TestCaseFunction's
178- # `obj` returns an *unbound* method (not attached to an instance).
179- # This inconsistency is probably not desirable, but needs some
180- # consideration before changing.
181- return getattr (self .parent .obj , self .originalname ) # type: ignore[attr-defined]
181+ assert isinstance (self .parent , UnitTestCase )
182+ testcase = self .parent .obj (self .name )
183+ return getattr (testcase , self .name )
184+
185+ # Backward compat for pytest-django; can be removed after pytest-django
186+ # updates + some slack.
187+ @property
188+ def _testcase (self ):
189+ return self ._obj .__self__
182190
183191 def setup (self ) -> None :
184192 # A bound method to be called during teardown() if set (see 'runtest()').
185193 self ._explicit_tearDown : Optional [Callable [[], None ]] = None
186- assert self .parent is not None
187- self ._testcase = self .parent .obj (self .name ) # type: ignore[attr-defined]
188- self ._obj = getattr (self ._testcase , self .name )
189194 super ().setup ()
190195
191196 def teardown (self ) -> None :
192197 super ().teardown ()
193198 if self ._explicit_tearDown is not None :
194199 self ._explicit_tearDown ()
195200 self ._explicit_tearDown = None
196- self ._testcase = None
197201 self ._obj = None
198202
199203 def startTest (self , testcase : "unittest.TestCase" ) -> None :
@@ -292,14 +296,14 @@ def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None:
292296 def runtest (self ) -> None :
293297 from _pytest .debugging import maybe_wrap_pytest_function_for_tracing
294298
295- assert self ._testcase is not None
299+ testcase = self .obj . __self__
296300
297301 maybe_wrap_pytest_function_for_tracing (self )
298302
299303 # Let the unittest framework handle async functions.
300304 if is_async_function (self .obj ):
301305 # Type ignored because self acts as the TestResult, but is not actually one.
302- self . _testcase (result = self ) # type: ignore[arg-type]
306+ testcase (result = self ) # type: ignore[arg-type]
303307 else :
304308 # When --pdb is given, we want to postpone calling tearDown() otherwise
305309 # when entering the pdb prompt, tearDown() would have probably cleaned up
@@ -311,16 +315,16 @@ def runtest(self) -> None:
311315 assert isinstance (self .parent , UnitTestCase )
312316 skipped = _is_skipped (self .obj ) or _is_skipped (self .parent .obj )
313317 if self .config .getoption ("usepdb" ) and not skipped :
314- self ._explicit_tearDown = self . _testcase .tearDown
315- setattr (self . _testcase , "tearDown" , lambda * args : None )
318+ self ._explicit_tearDown = testcase .tearDown
319+ setattr (testcase , "tearDown" , lambda * args : None )
316320
317321 # We need to update the actual bound method with self.obj, because
318322 # wrap_pytest_function_for_tracing replaces self.obj by a wrapper.
319- setattr (self . _testcase , self .name , self .obj )
323+ setattr (testcase , self .name , self .obj )
320324 try :
321- self . _testcase (result = self ) # type: ignore[arg-type]
325+ testcase (result = self ) # type: ignore[arg-type]
322326 finally :
323- delattr (self . _testcase , self .name )
327+ delattr (testcase , self .name )
324328
325329 def _traceback_filter (
326330 self , excinfo : _pytest ._code .ExceptionInfo [BaseException ]
0 commit comments