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
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
)