0

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?

1 Answer 1

1

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)

Sign up to request clarification or add additional context in comments.

5 Comments

thank you, sadly subtests do not seem to reset the database state between iterations and would require manually rolling back any changes to avoid false results. Your Mixin suggestion however worked!
Glad Mixin worked. I wrote some code off the top of my head, looked at it, though it wouldn't work...
"Manual" rollback in a subtest usually isn't hard. If it's just the DB you can probably use with transaction.atomic(), do your testing, raise an error to abort the transaction and then handle (pass) it outside the transaction and inside the subtest. A failed subtest will also abort its transaction.
I thought it wouldn't work either but figured trying it out wouldn't hurt... and here we are :D
oh I didn't know about with transaction.atomic(), I'll keep that in mind for when I need it. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.