Django: Best way to unit-test an abstract model

后端 未结 13 1345
一个人的身影
一个人的身影 2020-12-24 12:28

I need to write some unit tests for an abstract base model, that provides some basic functionality that should be used by other apps. It it would be necessary to define a mo

13条回答
  •  攒了一身酷
    2020-12-24 13:05

    Here is a working solution in django 3.0 with Postgres. It allows testing any number of abstract models and also maintains any integrity related to foreign objects.

    from typing import Union
    from django.test import TestCase
    from django.db import connection
    from django.db.models.base import ModelBase
    from django.db.utils import ProgrammingError
    
    # Category and Product are abstract models
    from someApp.someModule.models import Category, Product, Vendor, Invoice
    
    class MyModelsTestBase(TestCase):
        @classmethod
        def setUpTestData(cls):
            # keep track of registered fake models
            # to avoid RuntimeWarning when creating
            # abstract models again in the class
            cls.fake_models_registry = {}
    
        def setUp(self):
            self.fake_models = []
    
        def tearDown(self):
            try:
                with connection.schema_editor(atomic=True) as schema_editor:
                    for model in self.fake_models:
                        schema_editor.delete_model(model)
            except ProgrammingError:
                pass
    
        def create_abstract_models(self, models: Union[list, tuple]):
            """
            param models: list/tuple of abstract model class
            """
            # by keeping model names same as abstract model names
            # we are able to maintain any foreign key relationship
            model_names = [model.__name__ for model in models]
            modules = [model.__module__ for model in models]
            for idx, model_name in enumerate(model_names):
                # if we already have a ModelBase registered
                # avoid re-registering.
                registry_key = f'{modules[idx]}.{model_name}'
                model_base = self.fake_models_registry.get(registry_key)
                if model_base is not None:
                    self.fake_models.append(model_base)
                    continue
    
                # we do not have this model registered
                # so register it and track it in our
                # cls.fake_models_registry            
                self.fake_models.append(
                    ModelBase(
                        model_name,
                        (models[idx],),
                        {'__module__': modules[idx]}
                    )
                )
                self.fake_models_registry[registry_key] = self.fake_models[idx]
    
            errors = []
            # atomic=True allows creating multiple models in the db
            with connection.schema_editor(atomic=True) as schema_editor:
                try:
                    for model in self.fake_models:
                        schema_editor.create_model(model)
                 except ProgrammingError as e:
                     errors.append(e)
                     pass
            return errors
    
        def test_create_abstract_models(self):
            abstract_models = (Category, Product)
            errors = self.create_abstract_models(abstract_models)
            self.assertEqual(len(errors), 0)
    
            category_model_class, product_model_class = self.fake_models
    
            # and use them like any other concrete model class:
            category = category_model_class.objects.create(name='Pet Supplies')
            product = product_model_class.objects.create(
                name='Dog Food', category_id=category.id
            )
    
    
    

提交回复
热议问题