Django Test Migrations
django-test-migrations is a Python library designed for robustly testing Django schema and data migrations, ensuring they apply correctly, roll back safely, and maintain data integrity. It supports testing migration order and names, and works across multiple Django versions (currently 3.2, 4.1, 4.2, 5.0, 5.2). The library is actively maintained with regular updates for Python and Django version compatibility. The current version is 1.5.0, released on April 18, 2025.
Warnings
- breaking In version 1.0.0, the `Migrator` methods `before` and `after` were renamed to `apply_initial_migration` and `apply_tested_migration` respectively. Code using the old method names will break.
- gotcha Directly importing models into your migration tests (e.g., `from myapp.models import MyModel`) is a common pitfall. This can lead to unexpected behavior or failures as your tests won't be using the historical model state relevant to the specific migration being tested.
- gotcha When testing multiple Django applications that have interdependent migrations, you must provide `before` and `after` as a list of `(app_name, migration_name)` tuples. Additionally, when retrieving models, explicitly specify the app name (e.g., `self.get_model_before('otherapp.OtherModel')`).
- gotcha If your tests involve Django's `post_migrate` signals, be aware that `django-test-migrations` clears the receiver list at the start of tests and restores it afterwards. This is to prevent side effects.
- deprecated Support for older Python and Django versions has been dropped in recent releases. Specifically, Python 3.7, 3.8, and 3.9, and Django 2.2 are no longer officially supported.
Install
-
pip install django-test-migrations
Imports
- Migrator
from django_test_migrations.migrator import Migrator
- MigrationTest
from django_migration_testcase import MigrationTest
Quickstart
import os
import pytest
from django.conf import settings
# Minimal Django settings for testing if not already configured
# In a real project, this would typically be in your settings.py
if not settings.configured:
settings.configure(
INSTALLED_APPS=[
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Add your app here, e.g., 'myapp'
],
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
},
MIGRATION_MODULES={ # Example for a hypothetical app 'myapp'
# 'myapp': 'myapp.migrations' # Uncomment if your app has migrations
}
)
from django_test_migrations.migrator import Migrator
@pytest.fixture(scope='function')
def migrator(db) -> Migrator:
"""Provides a Migrator instance for testing against the 'default' database."""
return Migrator(database='default')
def test_my_migration(migrator: Migrator):
# Set up the database to a state *before* the migration you want to test
# Example: app_label 'myapp', migration name '0001_initial'
old_state = migrator.apply_initial_migration(('myapp', '0001_initial'))
# Get the model class from the old state
# Replace 'MyModel' with your actual model name
OldMyModel = old_state.apps.get_model('myapp', 'MyModel')
# Create some data that will be affected by the migration
# Example: creating an instance with an old field value
old_instance = OldMyModel.objects.create(some_old_field='value')
# Apply the migration you want to test
# Example: app_label 'myapp', migration name '0002_new_field'
new_state = migrator.apply_tested_migration(('myapp', '0002_new_field'))
# Get the model class from the new state
NewMyModel = new_state.apps.get_model('myapp', 'MyModel')
# Assertions: Check that the data was migrated correctly
new_instance = NewMyModel.objects.get(pk=old_instance.pk)
assert hasattr(new_instance, 'some_new_field') # Check for new field
# assert new_instance.some_new_field == 'expected_new_value'
# Optional: Test rollback
# migrator.reset()
# assert not NewMyModel.objects.filter(pk=old_instance.pk).exists() # Or check rolled back state