Chris Brown
Chris Brown

Reputation: 4635

Codeception & Symfony - run Doctrine migrations before tests

I have a Symfony 4 application and Doctrine with Doctrine migrations. I'm introducing Codeception for running API tests, and need to run migrations before the tests run. Since I'm using the Doctrine2 module I don't really want to be also including the DB module as it's not needed for the tests and would require configuring the test database in two different locations.

I am using the Symfony module currently, and I noticed that the Laravel module has a run_database_migrations configuration option.

What is the best way to handle running the Doctrine migrations command in a Symfony app prior to the tests? (bin/console doctrine:migrations:migrate -n is the specific command).


Edit I've got a solution that, although it works, is nowhere near ideal. By using Codeception Customisation I've created the following extension that basically manually execs the underlying Symfony commands.

class DatabaseMigrationExtension extends Extension
{
    public static $events = [
        Events::SUITE_BEFORE => 'beforeSuite',
    ];

    public function beforeSuite(SuiteEvent $e)
    {
        echo(exec('bin/console doctrine:database:drop --force') . PHP_EOL);
        echo(exec('bin/console doctrine:database:create') . PHP_EOL);
        echo(exec('bin/console doctrine:migrations:migrate -n') . PHP_EOL);
    }
}

Edit 2 The goal of this is basically to replicate similar functionality to what the Codeception DB module does, which allows you to provide an SQL dump of a database that it automatically uses in the tests, but instead use Doctrine migrations to handle the DB. - https://codeception.com/docs/modules/Db#sql-data-dump

Upvotes: 11

Views: 5769

Answers (4)

Nikita Kotov
Nikita Kotov

Reputation: 1

My decision:

<?php

declare(strict_types=1);

namespace Test\Support\Extension;

use Codeception\Event\SuiteEvent;
use Codeception\Events;
use Codeception\Extension;
use Codeception\Module\Symfony;

class DatabaseInitializationExtension extends Extension
{
    public static array $events = [Events::SUITE_INIT => ['initialize', 100]];

    public function initialize(SuiteEvent $event): void
    {
        $modules = $event->getSuite()->getModules();

        $symfony = $modules['Symfony'] ?? null;

        if ($symfony instanceof Symfony) {
            $symfony->runSymfonyConsoleCommand('d:d:drop', ['--force' => true, '--if-exists' => true]);
            $symfony->runSymfonyConsoleCommand('d:d:create', ['--if-not-exists' => true]);
            $symfony->runSymfonyConsoleCommand('d:m:m', ['-n' => true]);
        }
    }
}

Add to codeception.yml

extensions:
    enabled:
        - Test\Support\Extension\DatabaseInitializationExtension

Don't worry if --env=test is missing. If the environment: 'test' parameter is in the Symfony module, then the commands will be executed for the corresponding environment.

Upvotes: 0

Andriy Lozynskiy
Andriy Lozynskiy

Reputation: 2604

With Codeception 4 you can do it without Cli module:

$symfony = $this->getModule('Symfony');

$symfony->runSymfonyConsoleCommand('doctrine:database:drop',['--if-exists'=>true, '--force'=>true]);
$symfony->runSymfonyConsoleCommand('doctrine:database:create');
$symfony->runSymfonyConsoleCommand('doctrine:migrations:migrate', ['--no-interaction'=>true]);

Upvotes: 1

Chris Brown
Chris Brown

Reputation: 4635

I spent a while trying a couple of different ways to achieve this. I initially used RunProcess however this seemed to cause sporadic issues with the DB being deleted and not recreated, despite using the sleep configuration. I ended up just updating the existing extension to use the CLI module instead, and it works as desired (without having to create scripts or run multiple commands) and without having to use exec.

Final extension;

class DatabaseMigrationExtension extends Extension
{
    public static $events = [
        Events::SUITE_BEFORE => 'beforeSuite',
    ];

    public function beforeSuite()
    {
        try {
            /** @var \Codeception\Module\Cli $cli */
            $cli = $this->getModule('Cli');

            $this->writeln('Recreating the DB...');
            $cli->runShellCommand('bin/console doctrine:database:drop --if-exists --force');
            $cli->seeResultCodeIs(0);
            $cli->runShellCommand('bin/console doctrine:database:create');
            $cli->seeResultCodeIs(0);

            $this->writeln('Running Doctrine Migrations...');
            $cli->runShellCommand('bin/console doctrine:migrations:migrate --no-interaction');
            $cli->seeResultCodeIs(0);

            $this->writeln('Test database recreated');
        } catch (\Exception $e) {
            $this->writeln(
                sprintf(
                    'An error occurred whilst rebuilding the test database: %s',
                    $e->getMessage()
                )
            );
        }
    }
}

and registered;

// codeception.yml
extensions:
    enabled:
        - \DatabaseMigrationExtension

Output (-vv or higher also displays the output of the DB & Migration commands);

Upvotes: 10

sensorario
sensorario

Reputation: 21648

I always create a bash script to do this, or a Makefile.

bash command

My ./runtests.sh scripts contains

#!/bin/bash
./bin/console command:for:migrations
./bin/phpunit

Makefile

Same with Makefile

.FOO: testsuite
testsuite:
    ./runtests.sh

or

.FOO: testsuite
testsuite:
    ./bin/console command:for:migrations
    ./bin/phpunit

why I prefer Makefile

Recently I added this script in my .bash_profile that allow me to autocomplete from bash all target made in makefile (very easy because you dont need anymore to remember all commands, but just make and tab).

complete -W "`grep -oE '^[a-zA-Z0-9_.-]+:([^=]|$)' Makefile | sed 's/[^a-zA-Z0-9_.-]*$//'`" make

Thus, .. you can create target like:

  • runtests
  • runtests_with_fixtures
  • migrations
  • runtests_with_migrations
  • ...

and so on

My suggestion is to create your custom and easy way to run commands.


Here a way to run all or just one command usgin make

.FOO: dropforce
dropforce:
    bin/console doctrine:database:drop --force

.FOO: dbcreate
dbcreate:
    bin/console doctrine:database:create

.FOO: migrate
migrate:
    bin/console doctrine:migrations:migrate

.FOO: suite
suite: dropforce dbcreate migrate

Upvotes: 2

Related Questions