diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index 0429feee83..8e2b8db428 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -61,6 +61,15 @@ def test_subsequent_wait_calls(self): self.io_loop.add_timeout(self.io_loop.time() + 0.2, self.stop) self.wait(timeout=0.4) + def test_empty_instantation_is_allowed(self): + """ + Test that empty instatiation of an AsyncTestCase is allowed. + + unittest.TestCase docs guarantee this working, and pytest's unittest + support relies on it. + """ + AsyncTestCaseTest() + class LeakTest(AsyncTestCase): def tearDown(self): diff --git a/tornado/testing.py b/tornado/testing.py index bdbff87bc3..9455411a6d 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -177,7 +177,17 @@ def __init__(self, methodName: str = "runTest") -> None: # the test will silently be ignored because nothing will consume # the generator. Replace the test method with a wrapper that will # make sure it's not an undecorated generator. - setattr(self, methodName, _TestMethodWrapper(getattr(self, methodName))) + try: + test_method = getattr(self, methodName) + except AttributeError: + if methodName != "runTest": + # We allow instantiation with no explicit method name + # but not an *incorrect* or missing method name. + raise ValueError( + "no such test method in %s: %s" % (self.__class__, methodName) + ) + else: + setattr(self, methodName, _TestMethodWrapper(test_method)) # Not used in this class itself, but used by @gen_test self._test_generator = None # type: Optional[Union[Generator, Coroutine]]