Search code examples
phpsymfonydoctrine-ormdoctrinedoctrine-migrations

How to test Doctrine Migrations?


I'm working on a project that does NOT have a copy of production DB on development environment.

Sometimes we have an issue with DB migrations - they pass on dev DB but fail in production/testing.

It's often beacuse Dev environent data is loaded from Fixtures that use the latest entities - filling all tables properly.

Is there any easy way to make sure Doctrine Migration(s) will pass in production?

Do you have/know any way to write an automatic tests that will make sure data will be migrated properly without downloading the production/testing DB and running the migration manually?

I would like to avoid downloading a production/testing DB to dev machine so I can check migrations becasue that DB contains private data and it can be quite big.


Solution

  • First, you need to create a sample database dump in state before the migration. For MySQL use mysqldump. For postgres pg_dump, e.g.:

    mysqldump -u root -p mydatabase > dump-2018-02-20.sql
    pg_dump -Upostgres --inserts --encoding utf8 -f dump-2018-02-20.sql mydatabase
    

    Then create an abstract class for all migrations tests (I assume you have configured a separate database for integration testing in config_test.yml):

    abstract class DatabaseMigrationTestCase extends WebTestCase {
        /** @var ResettableContainerInterface */
        protected $container;
        /** @var Application */
        private $application;
    
        protected function setUp() {
            $this->container = self::createClient()->getContainer();
            $kernel = $this->container->get('kernel');
            $this->application = new Application($kernel);
            $this->application->setAutoExit(false);
            $this->application->setCatchExceptions(false);
    
            $em = $this->container->get(EntityManagerInterface::class);
            $this->executeCommand('doctrine:schema:drop --force');
            $em->getConnection()->exec('DROP TABLE IF EXISTS public.migration_versions');
        }
    
        protected function loadDump(string $name) {
            $em = $this->container->get(EntityManagerInterface::class);
            $em->getConnection()->exec(file_get_contents(__DIR__ . '/dumps/dump-' . $name . '.sql'));
        }
    
        protected function executeCommand(string $command): string {
            $input = new StringInput("$command --env=test");
            $output = new BufferedOutput();
            $input->setInteractive(false);
            $returnCode = $this->application->run($input, $output);
            if ($returnCode != 0) {
                throw new \RuntimeException('Failed to execute command. ' . $output->fetch());
            }
            return $output->fetch();
        }
    
        protected function migrate(string $toVersion = '') {
            $this->executeCommand('doctrine:migrations:migrate ' . $toVersion);
        }
    }
    

    Example migration test:

    class Version20180222232445_MyMigrationTest extends DatabaseMigrationTestCase {
        /** @before */
        public function prepare() {
            $this->loadDump('2018-02-20');
            $this->migrate('20180222232445');
        }
    
        public function testMigratedSomeData() {
            $em = $this->container->get(EntityManagerInterface::class);
            $someRow = $em->getConnection()->executeQuery('SELECT * FROM myTable WHERE id = 1')->fetch();
            $this->assertEquals(1, $someRow['id']);
            // check other stuff if it has been migrated correctly
        }
    }