{"id":13416044,"url":"https://github.com/wemake-services/django-test-migrations","last_synced_at":"2025-05-14T00:09:25.437Z","repository":{"id":37773813,"uuid":"223188953","full_name":"wemake-services/django-test-migrations","owner":"wemake-services","description":"Test django schema and data migrations, including migrations' order and best practices.","archived":false,"fork":false,"pushed_at":"2025-05-08T16:22:16.000Z","size":1200,"stargazers_count":548,"open_issues_count":16,"forks_count":32,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-08T17:35:37.809Z","etag":null,"topics":["django","django-migrations","django-orm","django-test","django-testing","pytest","pytest-plugin","python","python3"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/django-test-migrations/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wemake-services.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"wemake-services","custom":"https://boosty.to/sobolevn"}},"created_at":"2019-11-21T14:10:42.000Z","updated_at":"2025-05-08T16:22:13.000Z","dependencies_parsed_at":"2023-10-25T06:24:05.316Z","dependency_job_id":"a051025d-a1d8-47a5-ad66-b779fbfc59a0","html_url":"https://github.com/wemake-services/django-test-migrations","commit_stats":{"total_commits":402,"total_committers":18,"mean_commits":"22.333333333333332","dds":0.5572139303482587,"last_synced_commit":"60cd53f7cc2982d25e9dc2719c94a5290715795d"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wemake-services%2Fdjango-test-migrations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wemake-services%2Fdjango-test-migrations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wemake-services%2Fdjango-test-migrations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wemake-services%2Fdjango-test-migrations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wemake-services","download_url":"https://codeload.github.com/wemake-services/django-test-migrations/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253980965,"owners_count":21994178,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","django-migrations","django-orm","django-test","django-testing","pytest","pytest-plugin","python","python3"],"created_at":"2024-07-30T21:00:53.867Z","updated_at":"2025-05-14T00:09:25.397Z","avatar_url":"https://github.com/wemake-services.png","language":"Python","funding_links":["https://github.com/sponsors/wemake-services","https://boosty.to/sobolevn"],"categories":["Third-Party Packages","Migrations","Python"],"sub_categories":["Testing"],"readme":"# django-test-migrations\n\n[![wemake.services](https://img.shields.io/badge/%20-wemake.services-green.svg?label=%20\u0026logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC%2FxhBQAAAAFzUkdCAK7OHOkAAAAbUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2F5TvxDIAAAAIdFJOUwAjRA8xXANAL%2Bv0SAAAADNJREFUGNNjYCAIOJjRBdBFWMkVQeGzcHAwksJnAPPZGOGAASzPzAEHEGVsLExQwE7YswCb7AFZSF3bbAAAAABJRU5ErkJggg%3D%3D)](https://wemake-services.github.io)\n[![Build status](https://github.com/wemake-services/django-test-migrations/workflows/test/badge.svg?branch=master\u0026event=push)](https://github.com/wemake-services/django-test-migrations/actions?query=workflow%3Atest)\n[![codecov](https://codecov.io/gh/wemake-services/django-test-migrations/branch/master/graph/badge.svg)](https://codecov.io/gh/wemake-services/django-test-migrations)\n[![Python Version](https://img.shields.io/pypi/pyversions/django-test-migrations.svg)](https://pypi.org/project/django-test-migrations/)\n![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-test-migrations)\n[![wemake-python-styleguide](https://img.shields.io/badge/style-wemake-000000.svg)](https://github.com/wemake-services/wemake-python-styleguide)\n\n\n## Features\n\n- Allows to test `django` schema and data migrations\n- Allows to test both forward and rollback migrations\n- Allows to test the migrations order\n- Allows to test migration names\n- Allows to test database configuration\n- Fully typed with annotations and checked with `mypy`, [PEP561 compatible](https://www.python.org/dev/peps/pep-0561/)\n- Easy to start: has lots of docs, tests, and tutorials\n\nRead the [announcing post](https://sobolevn.me/2019/10/testing-django-migrations).\nSee real-world [usage example](https://github.com/wemake-services/wemake-django-template).\n\n\n## Installation\n\n```bash\npip install django-test-migrations\n```\n\nWe support several `django` versions:\n\n- `3.2`\n- `4.1`\n- `4.2`\n- `5.0`\n\nOther versions most likely will work too,\nbut they are not officially supported.\n\n\n## Testing Django migrations\n\nTesting migrations is not a frequent thing in `django` land.\nBut, sometimes it is totally required. When?\n\nWhen we do complex schema or data changes\nand what to be sure that existing data won't be corrupted.\nWe might also want to be sure that all migrations can be safely rolled back.\nAnd as a final touch, we want to be sure that migrations\nare in the correct order and have correct dependencies.\n\n### Testing forward migrations\n\nTo test all migrations we have a [`Migrator`](https://github.com/wemake-services/django-test-migrations/blob/master/django_test_migrations/migrator.py) class.\n\nIt has three methods to work with:\n\n- `.apply_initial_migration()` which takes app and migration names to generate\n  a state before the actual migration happens. It creates the `before state`\n  by applying all migrations up to and including the ones passed as an argument.\n\n- `.apply_tested_migration()` which takes app and migration names to perform the\n  actual migration\n\n- `.reset()` to clean everything up after we are done with testing\n\nSo, here's an example:\n\n```python\nfrom django_test_migrations.migrator import Migrator\n\nmigrator = Migrator(database='default')\n\n# Initial migration, currently our model has only a single string field:\n# Note:\n# We are testing migration `0002_someitem_is_clean`, so we are specifying\n# the name of the previous migration (`0001_initial`) in the\n# .apply_initial_migration() method in order to prepare a state of the database\n# before applying the migration we are going to test.\n#\nold_state = migrator.apply_initial_migration(('main_app', '0001_initial'))\nSomeItem = old_state.apps.get_model('main_app', 'SomeItem')\n\n# Let's create a model with just a single field specified:\nSomeItem.objects.create(string_field='a')\nassert len(SomeItem._meta.get_fields()) == 2  # id + string_field\n\n# Now this migration will add `is_clean` field to the model:\nnew_state = migrator.apply_tested_migration(\n    ('main_app', '0002_someitem_is_clean'),\n)\nSomeItem = new_state.apps.get_model('main_app', 'SomeItem')\n\n# We can now test how our migration worked, new field is there:\nassert SomeItem.objects.filter(is_clean=True).count() == 0\nassert len(SomeItem._meta.get_fields()) == 3  # id + string_field + is_clean\n\n# Cleanup:\nmigrator.reset()\n```\n\nThat was an example of a forward migration.\n\n### Backward migration\n\nThe thing is that you can also test backward migrations.\nNothing really changes except migration names that you pass and your logic:\n\n```python\nmigrator = Migrator()\n\n# Currently our model has two field, but we need a rollback:\nold_state = migrator.apply_initial_migration(\n    ('main_app', '0002_someitem_is_clean'),\n)\nSomeItem = old_state.apps.get_model('main_app', 'SomeItem')\n\n# Create some data to illustrate your cases:\n# ...\n\n# Now this migration will drop `is_clean` field:\nnew_state = migrator.apply_tested_migration(('main_app', '0001_initial'))\n\n# Assert the results:\n# ...\n\n# Cleanup:\nmigrator.reset()\n```\n\n### Testing migrations ordering\n\nSometimes we also want to be sure that our migrations are in the correct order\nand that all our `dependencies = [...]` are correct.\n\nTo achieve that we have [`plan.py`](https://github.com/wemake-services/django-test-migrations/blob/master/django_test_migrations/plan.py) module.\n\nThat's how it can be used:\n\n```python\nfrom django_test_migrations.plan import all_migrations, nodes_to_tuples\n\nmain_migrations = all_migrations('default', ['main_app', 'other_app'])\nassert nodes_to_tuples(main_migrations) == [\n    ('main_app', '0001_initial'),\n    ('main_app', '0002_someitem_is_clean'),\n    ('other_app', '0001_initial'),\n    ('main_app', '0003_update_is_clean'),\n    ('main_app', '0004_auto_20191119_2125'),\n    ('other_app', '0002_auto_20191120_2230'),\n]\n```\n\nThis way you can be sure that migrations\nand apps that depend on each other will be executed in the correct order.\n\n### `factory_boy` integration\n\nIf you use factories to create models, you can replace their respective\n`.build()` or `.create()` calls with methods of `factory` and pass the\nmodel name and factory class as arguments:\n\n```python\nimport factory\n\nold_state = migrator.apply_initial_migration(\n    ('main_app', '0002_someitem_is_clean'),\n)\nSomeItem = old_state.apps.get_model('main_app', 'SomeItem')\n\n# instead of\n# item = SomeItemFactory.create()\n# use this:\nfactory.create(SomeItem, FACTORY_CLASS=SomeItemFactory)\n\n# ...\n```\n\n\n## Test framework integrations 🐍\n\nWe support several test frameworks as first-class citizens.\nThat's a testing tool after all!\n\nNote that the Django `post_migrate` signal's receiver list is cleared at\nthe start of tests and restored afterwards. If you need to test your\nown `post_migrate` signals then attach/remove them during a test.\n\n### pytest\n\nWe ship `django-test-migrations` with a `pytest` plugin\nthat provides two convenient fixtures:\n\n- `migrator_factory` that gives you an opportunity\n  to create `Migrator` classes for any database\n- `migrator` instance for the `'default'` database\n\nThat's how it can be used:\n\n```python\nimport pytest\n\n@pytest.mark.django_db\ndef test_pytest_plugin_initial(migrator):\n    \"\"\"Ensures that the initial migration works.\"\"\"\n    old_state = migrator.apply_initial_migration(('main_app', None))\n\n    with pytest.raises(LookupError):\n        # Model does not yet exist:\n        old_state.apps.get_model('main_app', 'SomeItem')\n\n    new_state = migrator.apply_tested_migration(('main_app', '0001_initial'))\n    # After the initial migration is done, we can use the model state:\n    SomeItem = new_state.apps.get_model('main_app', 'SomeItem')\n    assert SomeItem.objects.filter(string_field='').count() == 0\n```\n\n### unittest\n\nWe also ship an integration with the built-in `unittest` framework.\n\nHere's how it can be used:\n\n```python\nfrom django_test_migrations.contrib.unittest_case import MigratorTestCase\n\nclass TestDirectMigration(MigratorTestCase):\n    \"\"\"This class is used to test direct migrations.\"\"\"\n\n    migrate_from = ('main_app', '0002_someitem_is_clean')\n    migrate_to = ('main_app', '0003_update_is_clean')\n\n    def prepare(self):\n        \"\"\"Prepare some data before the migration.\"\"\"\n        SomeItem = self.old_state.apps.get_model('main_app', 'SomeItem')\n        SomeItem.objects.create(string_field='a')\n        SomeItem.objects.create(string_field='a b')\n\n    def test_migration_main0003(self):\n        \"\"\"Run the test itself.\"\"\"\n        SomeItem = self.new_state.apps.get_model('main_app', 'SomeItem')\n\n        assert SomeItem.objects.count() == 2\n        assert SomeItem.objects.filter(is_clean=True).count() == 1\n```\n\n### Choosing only migrations tests\n\nIn CI systems it is important to get instant feedback. Running tests that\napply database migration can slow down tests execution, so it is often a good\nidea to run standard, fast, regular unit tests without migrations in parallel\nwith slower migrations tests.\n\n#### pytest\n\n`django_test_migrations` adds `migration_test` marker to each test using\n`migrator_factory` or `migrator` fixture.\nTo run only migrations test, use `-m` option:\n\n```bash\npytest -m migration_test  # Runs only migration tests\npytest -m \"not migration_test\"  # Runs all except migration tests\n```\n\n#### unittest\n\n`django_test_migrations` adds `migration_test`\n[tag](https://docs.djangoproject.com/en/3.0/topics/testing/tools/#tagging-tests)\nto every `MigratorTestCase` subclass.\nTo run only migrations tests, use `--tag` option:\n\n```bash\npython mange.py test --tag=migration_test  # Runs only migration tests\npython mange.py test --exclude-tag=migration_test  # Runs all except migration tests\n```\n\n\n## Django Checks\n\n`django_test_migrations` comes with 2 groups of Django's checks for:\n\n+ detecting migrations scripts automatically generated names\n+ validating some subset of database settings\n\n### Testing migration names\n\n`django` generates migration names for you when you run `makemigrations`.\nThese names are bad ([read more](https://adamj.eu/tech/2020/02/24/how-to-disallow-auto-named-django-migrations/) about why it is bad)!\nJust look at this: `0004_auto_20191119_2125.py`\n\nWhat does this migration do? What changes does it have?\n\nOne can also pass `--name` attribute when creating migrations, but it is easy to forget.\n\nWe offer an automated solution: `django` check\nthat produces an error for each badly named migration.\n\nAdd our check into your `INSTALLED_APPS`:\n\n```python\nINSTALLED_APPS = [\n    # ...\n\n    # Our custom check:\n    'django_test_migrations.contrib.django_checks.AutoNames',\n]\n```\n\nThen in your CI run:\n\n```bash\npython manage.py check --deploy\n```\n\nThis way you will be safe from wrong names in your migrations.\n\nDo you have a migrations that cannot be renamed? Add them to the ignore list:\n\n```python\n# settings.py\n\nDTM_IGNORED_MIGRATIONS = {\n    ('main_app', '0004_auto_20191119_2125'),\n    ('dependency_app', '0001_auto_20201110_2100'),\n}\n```\n\nThen we won't complain about them.\n\nOr you can completely ignore entire app:\n\n```python\n# settings.py\n\nDTM_IGNORED_MIGRATIONS = {\n    ('dependency_app', '*'),\n    ('another_dependency_app', '*'),\n}\n```\n\n### Database configuration\n\nAdd our check to `INSTALLED_APPS`:\n\n```python\nINSTALLED_APPS = [\n    # ...\n\n    # Our custom check:\n    'django_test_migrations.contrib.django_checks.DatabaseConfiguration',\n]\n```\n\nThen just run `check` management command in your CI like listed in section\nabove.\n\n\n## Related projects\n\nYou might also like:\n\n- [django-migration-linter](https://github.com/3YOURMIND/django-migration-linter) - Detect backward incompatible migrations for your django project.\n- [wemake-django-template](https://github.com/wemake-services/wemake-django-template/) - Bleeding edge django template focused on code quality and security with both `django-test-migrations` and `django-migration-linter` on board.\n\n\n## Credits\n\nThis project is based on work of other awesome people:\n\n- [@asfaltboy](https://gist.github.com/asfaltboy/b3e6f9b5d95af8ba2cc46f2ba6eae5e2)\n- [@blueyed](https://gist.github.com/blueyed/4fb0a807104551f103e6)\n- [@fernandogrd](https://gist.github.com/blueyed/4fb0a807104551f103e6#gistcomment-1546191)\n- [@adamchainz](https://adamj.eu/tech/2020/02/24/how-to-disallow-auto-named-django-migrations/)\n\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwemake-services%2Fdjango-test-migrations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwemake-services%2Fdjango-test-migrations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwemake-services%2Fdjango-test-migrations/lists"}