Skip to content

Instantly share code, notes, and snippets.

@GeeWee
Last active February 1, 2023 20:26
Show Gist options
  • Save GeeWee/54b6fd7ad87bcc876781ae02d1e0993d to your computer and use it in GitHub Desktop.
Save GeeWee/54b6fd7ad87bcc876781ae02d1e0993d to your computer and use it in GitHub Desktop.
TEST_TENANT_NAME = "test"
@pytest.fixture(scope="function")
def db(request, django_db_setup, django_db_blocker):
from django.test import TestCase, TransactionTestCase
"""
Django db fixture.
Seeing as this has the same signature, it overrides the fixture in pytest_django.fixtures
It doesn't support quite the same things, such as transactional tests or resetting of sequences.
The way our setup works here, is that due to performance reasons we want to override this, and then
create our public and test tenant, before entering into the `atomic` block that pytest and Django normally
runs tests in - this way we only have to do the heavy work of migrating our schemas once every test run, rather
than every test.
"""
logger.info("Fetching the DB fixture")
if is_django_unittest(request):
return
# Some weird django db_blocker magic
django_db_blocker.unblock()
request.addfinalizer(django_db_blocker.restore)
# Create the public and the test tenant.
# We do this right before the pre_setup so it doesn't
# get axed by the atomic block()
Tenant.objects.get_or_create(schema_name=get_public_schema_name())
_get_or_create_test_tenant()
""" Here we distinguish between a transactional test or not (corresponding to Djangos
TestCase or its TransactionTestCase. Some tests that create/drop tenants can't be run
inside an atomic block, so must be marked as transactional"""
if "transactional" in request.keywords:
test_case = TransactionTestCase(methodName="__init__")
logger.debug("Using transactional test case")
else:
# This performs the rest of the test in an atomic() block which will roll back the changes.
logger.debug("Using regular test case")
test_case = TestCase(methodName="__init__")
test_case._pre_setup()
# Post-teardown function here reverts the atomic blocks to leave the DB in a fresh state.
request.addfinalizer(test_case._post_teardown) # This rolls the atomic block back
@pytest.fixture(scope="function")
def user(run_in_tenant_context) -> User:
from django.contrib.auth import get_user_model
from model_mommy import mommy
return mommy.make(get_user_model(), tenant=_get_or_create_test_tenant())
@pytest.fixture(scope="function")
def api_client(user) -> APIClient:
""" Returns a logged in APIClient from DRF. Creates a user-model as a side-effect."""
logger.info("Fetching api_client fixture")
client = APIClient()
client.force_login(user)
# Run test
return client
@pytest.fixture(scope="function", autouse=True)
def run_in_tenant_context(db, request, caplog):
# Here we create the statics
logger.info("Running in test tenant context")
with tenant_context(_get_or_create_test_tenant()):
# Delete unknown offloading ServiceType from datamigration
ServiceType.objects.all().delete()
# Create statics if needed
if "no_statics" not in request.keywords:
# We don't want to show setup logs normally
with caplog.at_level(logging.WARNING):
logger.info("Populating static tables")
populate_static_database_tables()
yield
def _get_or_create_test_tenant() -> Tenant:
"""
Fixture that gives us a test_tenant if we need it
"""
try:
tenant = Tenant.objects.get(schema_name=TEST_TENANT_NAME)
logger.debug("Using previously created tenant")
return tenant
except Tenant.DoesNotExist:
logger.debug("Creating new test tenant")
tenant = Tenant(schema_name=TEST_TENANT_NAME)
tenant.save(verbosity=0) # This saves the tenant and creates the tenant schema.
return tenant
@GeeWee
Copy link
Author

GeeWee commented Jul 28, 2021

Can't really recall why I omitted the import statements anymore.
ServiceType is just a application-specific thing for my app. You can omit that line and it should work just fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment