Skip to content

feat: MRTTestCase for unittest-based Django migration testing#61

Merged
croc100 merged 6 commits into
mainfrom
feat/django-migratortestcase
Jun 9, 2026
Merged

feat: MRTTestCase for unittest-based Django migration testing#61
croc100 merged 6 commits into
mainfrom
feat/django-migratortestcase

Conversation

@croc100

@croc100 croc100 commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds MRTTestCase(unittest.TestCase) — drop-in base class for Django teams not using pytest
  • assertRollbackSafe() delegates to DjangoRollbackVerifier (full seed+upgrade+downgrade+verify cycle)
  • assertDataIntact() verifies both user-created rows AND SmartSeeder rows survive rollback
  • old_apps / apps properties expose historical Django app registries for each migration state

Bug fixes in this PR

# Bug Fix
1 assertDataIntact only checked SmartSeeder rows, not user ORM data PK snapshot before seeder; verifies both sets after rollback
2 No recovery if downgrade() raised mid-test try/exceptdowngrade_app_zero fallback, then re-raise
3 _historical_apps hardcoded connections["default"] @classmethod reads cls.db_alias (default: "default"); override for multi-DB
4 __init__.py imported MRTTestCase unconditionally Wrapped in try/except ImportError — safe for non-Django users

Test plan

  • 12 unit tests in tests/test_django_testcase.py — all mocked, no real DB required
  • New: test_assert_data_intact_recovery_on_downgrade_failure — verifies downgrade_app_zero is called
  • New: test_assert_data_intact_detects_user_row_loss — verifies pre-existing rows are tracked
  • Full suite: 434 passed, 17 skipped

Usage

from pytest_mrt.django_testcase import MRTTestCase

class TestRemovePhoneField(MRTTestCase):
    db_url       = "sqlite:///test.db"
    migrate_from = ("myapp", "0009_add_phone")
    migrate_to   = ("myapp", "0010_remove_phone")

    def test_rollback_is_safe(self):
        self.assertRollbackSafe()

    def test_existing_rows_survive(self):
        User = self.old_apps.get_model("myapp", "User")
        User.objects.create(name="Alice", phone="+1-555-0100")
        self.assertDataIntact()   # Alice must still be there after rollback

croc100 added 3 commits June 9, 2026 20:57
Setup section previously showed users they must write a test file.
Restructured to lead with the zero-config value: configure MRTConfig
and 6 safety tests appear automatically with no test files needed.
Removed the duplicate "Built-in default tests" section buried in the
lower half of the README.
Allows Django teams not using pytest to test migration rollback safety
with manage.py test, matching the API style of django-test-migrations.

    from pytest_mrt.django_testcase import MRTTestCase

    class TestRemovePhone(MRTTestCase):
        db_url       = "sqlite:///test.db"
        migrate_from = ("myapp", "0009_add_phone")
        migrate_to   = ("myapp", "0010_remove_phone")

        def test_rollback_is_safe(self):
            self.assertRollbackSafe()

        def test_existing_rows_survive(self):
            User = self.old_apps.get_model("myapp", "User")
            User.objects.create(name="Alice", phone="+1-555-0100")
            self.assertDataIntact()

- assertRollbackSafe(): full pytest-mrt check (seed + upgrade + downgrade
  + schema/data integrity verification)
- assertDataIntact(): user-seeded data check after manual setup
- old_apps / apps: historical Django app registry at migrate_from / migrate_to
- setUpClass migrates to migrate_from; tearDownClass rolls back to zero
- setUp restores migrate_from state before each test method
- assertDataIntact now snapshots pre-existing PKs before SmartSeeder runs,
  then verifies user-created rows survived rollback (not just seeder rows)
- add try/except recovery in assertDataIntact: on downgrade failure,
  downgrade_app_zero is called so the next test starts from a clean state
- _historical_apps changed from @staticmethod to @classmethod and reads
  cls.db_alias (default "default") instead of hardcoding connections["default"]
- add db_alias class attribute to MRTTestCase for multi-DB projects
- guard MRTTestCase import in __init__.py with try/except ImportError so
  non-Django users are never affected by future django_testcase dependencies
- add two new unit tests: recovery path and user-row-loss detection
@codecov-commenter

codecov-commenter commented Jun 9, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 77.45098% with 23 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pytest_mrt/django_testcase.py 77.89% 16 Missing and 5 partials ⚠️
pytest_mrt/__init__.py 71.42% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

croc100 added 3 commits June 9, 2026 21:32
- use redundant alias pattern (as X) for re-exports in __init__.py
- fix import sort order in django_testcase.py (sqlalchemy before local)
- remove unused `connect_calls` variable in test
- remove unused SmartSeeder imports in test functions
@croc100 croc100 merged commit 6442fae into main Jun 9, 2026
14 checks passed
@croc100 croc100 deleted the feat/django-migratortestcase branch June 9, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants