Django tricks: how to exclude a single migration during unit tests
Have you ever written the perfect migration for your production & staging servers, but the same migration doesn’t work in your unit testing environment? This article will provide a neat trick to allow you to select specific migration files to be skipped by the unit testing framework.
When & why this would be useful
This recently came up where I had to remove a pesky database constraint from a legacy app we recently converted into a Django app. The database constraint could very easily be replaced with the Django choices
field parameter, so I wrote a quick SQL migration to drop the constraint from the database.
When I tested this new migration in my dev instance, it ran without issues and did what it was supposed to. Unfortunately, when I ran the unit tests they failed because the same constraint did not exist within the unit test environment which starts out as a pristine baggage-free database, then runs all the migrations on top.
I needed a way to tell Django to skip my new migration file for the unit tests because this migration would only ever be run in production and staging, and doesn’t work in the unit test environment.
How to implement a “skip this migration” check
I should warn that this strategy should be used sparingly, as it is generally extremely useful to run the migrations in the unit test environment to help validate those migrations, as well as ensuring all the app features still function properly after running the migration. However, if you ever find yourself in the same situation I did, here is how I worked around it.
The trick is quite straightforward: use the python ternary operator to automatically detect whether we are running unit tests by checking the system execution params, and if so then replace the operation list with an empty list. For example:
from django.db import migrations
import sys
class Migration(migrations.Migration):
dependencies = [
('myapp', '0005_auto_20231117_1813'),
]
operations = [
migrations.RunSQL("ALTER TABLE my_table DROP CONSTRAINT my_custom_constraint;")
] if 'test' not in sys.argv else []
Alternatively if you have a boolean constant for determining the unit test environment in your settings.py
for the project, you can import that constant and check on that value instead.
It’s that simple!
As noted above, skipping migrations should be avoided if at all possible, and this solution is provided for those edge situations where it cannot be avoided.