Exploring Django test classes

As I was creting some tests for the application I’ve noticed that the creation of users was only being repeated, so I’ve decided to create some sorts of project level Test Class:

@timed is a decorator to get the function runtime

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)

and then extending this class in app level:

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 then the actual test classes:

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

With all this set, and running $ find . -name “*.pyc” -delete && python manage.py test app.tests.test_views.TestViewsGET --verbosity 3

It “finds” only 1, when theres 4 tests

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 <module>
    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/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  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/core/management/commands/test.py", line 68, in handle
    failures = test_runner.run_tests(test_labels)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  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’ve also tried to @overload the test_* methods inside of TestAppView:

    @overload
    def test_unlogged_user_redirects_to_login(self) -> None: ...

But still the same error occurs.

The error message indicates that your TestViewsGET class is not finding any test methods to execute. In the context of Python’s unittest framework, test methods should be prefixed with the string test_.

For example, if you have this method:

def unlogged_user_redirects_to_login(self):
    #test code

it should have the prefix test_:

def test_unlogged_user_redirects_to_login(self):
    # test code here

I hope it helps.

hi @anefta , all the test methods starts with test_

It would be better if you post all the code you are having trouble with. Make sure to include your import statements also.