I've been refactoring Django tests by creating a project-level test base class to avoid repeating user creation code, but when I run the tests only one test is discovered and an error is raised. My setup involves a custom TestBaseView that handles user creation and login along with app-specific setup, which I then extend in an app-level class. Here’s my code:
class TestBaseView(TestCase):
app_name: str # Ensures app_name is a defined attribute
def __init__(self, app_name: str, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.app_name = app_name
@classmethod
def setUpTestData(cls) -> None:
"""Set up test data common to all test cases."""
print(f'\n {"="*10} {cls.__name__} {"="*10}')
cls.default_user, cls.manager_user = cls._create_users(cls.get_app_name())
(
cls.unlogged_client,
cls.default_client,
cls.manager_client,
) = Client(), Client(), Client()
cls._log_user_in(cls.default_client, cls.default_user)
cls._log_user_in(cls.manager_client, cls.manager_user)
cls.create_app_specific_objects()
super().setUpTestData()
def setUp(self):
"""Set up for each individual test method."""
super().setUp()
self.temp_data = {}
def tearDown(self):
"""Clean up after each test method."""
self.temp_data.clear()
super().tearDown()
@classmethod
def get_app_name(cls) -> str:
"""
Gets the app name. Needs override, used by other methods.
## Usage:
>>> return "example_app"
"""
raise NotImplementedError(
"Child classes must implement 'get_app_name'."
)
@classmethod
def _create_users(cls, app_name: str) -> Tuple[object, object]:
"""Create and return default and manager user objects."""
return _user_factory(app_name)
@classmethod
def create_app_specific_objects(cls) -> None:
"""Create app-specific objects. Must be implemented in child classes."""
raise NotImplementedError(
"Child classes must implement 'create_app_specific_objects'."
)
@classmethod
def _log_user_in(cls, client: object, user: object) -> None:
client.force_login(user)
Then, I extend this class in the app-level test class:
class TestAppView(TestBaseView):
@classmethod
def setUpTestData(cls):
super().setUpTestData()
# cls.create_app_specific_objects()
@classmethod
def get_app_name(cls):
return "app"
@classmethod
@timed
def create_app_specific_objects(cls):
# some app related object creation
And the actual test class is as follows:
class TestViewsGET(TestAppView):
@classmethod
def setUpTestData(cls):
super().setUpTestData()
def setUp(self):
print("# setUp ###############################################")
# Initialize clients
super().setUp()
self.rh_client = Client()
self.rh_client.force_login(self.rh_user)
self.rh_manager_client = Client()
self.rh_manager_client.force_login(self.rh_manager_user)
# With methods following test discovery
def test_*(self):
# Test cases here
I run the test command as follows:
find . -name "*.pyc" -delete && python manage.py test app.tests.test_views.TestViewsGET --verbosity 3
The output shows only one test is discovered along with the following error:
bash
Found 1 test(s).
System check identified no issues (0 silenced).
========== TestViewsGET ==========
Function '_update_groups' runtime: 0:00:00.189321
Function '_update_groups' runtime: 0:00:15.623892
Function '_user_factory' runtime: 0:00:16.392986
Function 'create_app_specific_objects' runtime: 0:00:01.654744
Traceback (most recent call last):
File "/home/webapp/manage.py", line 22, in
main()
File "/home/webapp/manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "/home/webapp/.venv/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
utility.execute()
File "/home/webapp/.venv/lib/python3.11/site-packages/django/core/management/commands/test.py", line 24, in run_from_argv
super().run_from_argv(argv)
File "/home/webapp/.venv/lib/python3.11/site-packages/django/core/management/base.py", line 412, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/webapp/.venv/lib/python3.11/site-packages/django/core/management/base.py", line 458, in execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/webapp/.venv/lib/python3.11/site-packages/django/test/runner.py", line 1068, in run_tests
result = self.run_suite(suite)
File "/home/webapp/.venv/lib/python3.11/site-packages/django/test/runner.py", line 995, in run_suite
return runner.run(suite)
File "/usr/lib/python3.11/unittest/runner.py", line 217, in run
test(result)
File "/usr/lib/python3.11/unittest/suite.py", line 84, in __call__
return self.run(*args, **kwds)
File "/usr/lib/python3.11/unittest/suite.py", line 122, in run
test(result)
File "/home/webapp/.venv/lib/python3.11/site-packages/django/test/testcases.py", line 258, in __call__
self._setup_and_call(result)
File "/home/webapp/.venv/lib/python3.11/site-packages/django/test/testcases.py", line 273, in _setup_and_call
testMethod = getattr(self, self._testMethodName)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'TestViewsGET' object has no attribute 'runTest'. Did you mean: 'subTest'?
I’m looking for insights on why test discovery is only registering one test and how to resolve the runTest attribute error in this custom test setup. Any help would be appreciated!