[Django]-Testing abstract models – django 2.2.4 / sqlite3 2.6.0

3👍

Simple Solution

So after a little digging and testing it seems that it’s best to let django’s TestCase close the transaction normally and then remove the model from the test database. Essentially we just call the super().tearDownClass() first instead of last.

ModelMixinTestCase

Since it’s a useful class I’ll post the full class for others to copy/paste.

class ModelMixinTestCase(TestCase):
    """
    Test Case for abstract mixin models.
    Subclass and set cls.mixin to your desired mixin.
    access your model using cls.model.
    """
    mixin = None
    model = None

    @classmethod
    def setUpClass(cls) -> None:
        # Create a real model from the mixin
        cls.model = ModelBase(
            "__Test" + cls.mixin.__name__,
            (cls.mixin,),
            {'__module__': cls.mixin.__module__}
        )

        # Use schema_editor to create schema
        with connection.schema_editor() as editor:
            editor.create_model(cls.model)

        super().setUpClass()

    @classmethod
    def tearDownClass(cls) -> None:
        # allow the transaction to exit
        super().tearDownClass()

        # Use schema_editor to delete schema
        with connection.schema_editor() as editor:
            editor.delete_model(cls.model)

        # close the connection
        connection.close()

Example Usage 1

class MyMixinTestCase(ModelMixinTestCase):
    mixin = MyMixin

    def test_true(self):
        self.assertTrue(True)

Example Usage 2

class SortableModelMixinTestCase(ModelMixinTestCase):
    mixin = SortableModelMixin

    def setUp(self) -> None:
        self.objects = [self.model.objects.create(pk=i) for i in range(10)]

    def test_order_linear(self) -> None:
        i = 1
        for item in self.objects:
            self.assertEqual(i, item.sortable_order)
            i += 1
👤bob

0👍

@bob Thanks for the good solution, this worked great.

But when reusing the mixin on a different TestCase with the same model I ran into a warning:

RuntimeWarning: Model '<model_name>' was already registered. Reloading models is not advised as it can lead to inconsistencies, most notably with related models.

I solved this by checking if the model was already registered in the app:

@classmethod
def setUpClass(cls) -> None:
    # Create a real model from the mixin or
    # get it from the reqistered models if it already exist
    model_name = "__Test" + cls.mixin.__name__
    for app, models in apps.all_models.items():
        if model_name.lower() in models:
            cls.model = models[model_name.lower()]
    if not hasattr(cls, "model"):
        cls.model = ModelBase(
            model_name, (cls.mixin,),
            {'__module__': cls.mixin.__module__}
        )

Leave a comment