wemake-services / django-test-migrations

Test django schema and data migrations, including migrations' order and best practices.
https://pypi.org/project/django-test-migrations/
MIT License
516 stars 31 forks source link

Cannot have multiple test functions in the same class inheriting from MigratorTestCase #467

Open jrobichaud opened 1 month ago

jrobichaud commented 1 month ago

The way MigratorTestCase is implemented it is only possible to setup test data in the prepare function which is called during setUp. Because of that there is no way to setup different data for different test functions.

In order to be able to support multiple test functions MigratorTestCase could be implemented this way:


@tag(MIGRATION_TEST_MARKER)
class MigratorTestCase(TransactionTestCase):
    """Used when using raw ``unitest`` library for test."""

    database_name: ClassVar[Optional[str]] = None
    old_state: ProjectState
    new_state: ProjectState

    #: Part of the end-user API. Used to tell what migrations we are using.
    migrate_from: ClassVar[MigrationSpec]
    migrate_to: ClassVar[MigrationSpec]

    auto_migrate: bool = True  # <-------------- Add an option to disable automatic migration

    def setUp(self) -> None:
        """
        Regular ``unittest`` styled setup case.

        What it does?
          - It starts with defining the initial migration state
          - Then it allows to run custom method
            to prepare some data before the migration will happen
          - Then it applies the migration and saves all states

        """
        super().setUp()
        self._migrator = Migrator(self.database_name)
        self.old_state = self._migrator.apply_initial_migration(
            self.migrate_from,
        )
        self.prepare()
        if self.auto_migrate:   # <-------------- check if auto_migrate is activated
            self.migrate()   # <-------------- call the extracted the method

    # v-------------- do the migration
    def migrate(self) -> None:
        assert not hasattr(self, "new_state"), "migrate must be called only once"
        self.new_state = self._migrator.apply_tested_migration(self.migrate_to)

it would allow to add multiple test scenarios like this:

class Test1234(MigratorTestCase):
    migrate_from = ...
    migrate_to = ...

    auto_migrate = False

    def prepare(self):
        ... # prepare common stuff here

    def test_scenario_1(self):
        ... # instantiate stuff here using old state
        self.migrate()
        ... # validate migration here using new state

    def test_scenario_2(self):
        ... # instantiate stuff here using old state
        self.migrate()
        ... # validate migration here using new state
jrobichaud commented 1 month ago

The workaround for the moment is to create a custom MigratorTestCase.

from django_test_migrations.contrib.unittest_case import MigratorTestCase
from django_test_migrations.migrator import Migrator

class ManualMigratorTestCase(MigratorTestCase):
    def setUp(self) -> None:
        self._migrator = Migrator(self.database_name)
        self.old_state = self._migrator.apply_initial_migration(
            self.migrate_from,
        )
        self.prepare()

    def migrate(self) -> None:
        assert not hasattr(self, "new_state"), "migrate must be called only once"
        self.new_state = self._migrator.apply_tested_migration(self.migrate_to)
sobolevn commented 1 month ago

Maybe we should design a new API instead? We can come up with a better one and deprecate the existing one.

jrobichaud commented 1 month ago

Before using django-test-migrations, I was using django_migration_testcase and it is how it was working.

django-test-migrations is much more robust in my opinion but I still miss this behaviour of that library.