Search code examples
pythondjangopython-unittestdjango-testing

Defining test methods in base class which should not be instanciated directly


I am trying to create test cases which only differ by which user is logged in. Therefore I figured defining the tests in a base class and creating sub classes which log in the user during setUp() would be the way to go.

However I cannot find an elegant solution to excluding the tests in the base class from running.

from django.contrib.auth.models import User
from django.test import TestCase


class A(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.user_a = User.objects.create_user("a", "", "a")
        cls.user_b = User.objects.create_user("b", "", "b")

    def setUp(self):
        # global set up stuff here
        ...

    def test_1(self):
        self.assertEqual(self.user_a.username, "a")


class SubA(A):
    def setUp(self):
        super().setUp()
        self.client.force_login(self.user_a)


class SubB(A):
    def setUp(self):
        super().setUp()
        self.client.force_login(self.user_b)

In the code above I'd want only the inherited tests in the classes SubA and SubB to run, but not the one in A.

I have tried:

  • extracting the base class to another module, which is not included by the default search pattern. => still runs the tests since the base class must be imported to inherit from it
  • defining a load_tests(...) function in the package which excludes the base class => same result as above
  • raising unittest.SkipTest in the base class setUp() and catching it in the sub classes' setUp => works but adds all tests in the base class as "skipped"

So my question is: Is there a way to exclude the tests in a base class (which must inherit from Django's TestCase) from running without recording them as skipped and, preferably, without the need for additional parameters to my ./manage.py test command?


Solution

  • Maybe use the subTest facility of the test framework?

    class A(TestCase):
        ... # setups as before
    
        def test1( self):
            for user in ( self.user_a, self.user_b):
                with self.subTest( user=user):
                    self.client.force_login( user)
    
                    # and now the test(s) that you want to execute both for user_a and user_b
                    self.assertEqual(user.username, "a")
    

    If a subtest fails, the test containing it does not stop and the next subtest in the iteration starts. The kwarg to self.subTest is printed as part of the subtest failure message. If (as here) the passed entity is not a string, it's converted to str. If this will fail or generate stupid output you would do your own conversion, such as self.subTest( user=f"{user.username} (pk={user.pk})" )

    (if you saw this five minutes ago, I have deleted my other suggestion because on reflection I didn't see it would be helpful. In case I am wrong, the suggestion was that you could put the test routines in a mixin class derived from object, and inherit them into the TestCase classes A and B)