There are better solutions to accomplish what I describe in this article. I'd like to recommend the ICBaseTestBundle here, which automatically creates a database for you and loads fixture data into it.

One of the beautiful things about PHPUnit is the way it can be easily configured: for example you can alter the paths in which PHPUnit will look for TestCase classes. In your Symfony2 project this can be done in the file /app/phpunit.xml(if it is called phpunit.xml.dist, rename it to phpunit.xml first!). This file contains a default configuration which suffices in most situations, but in case your TestCase classes are housed somewhere else than in your *Bundle/Test, add an extra "directory" to the "testsuite" element in phpunit.xml:

<phpunit>
    <testsuites>
        <testsuite name="Project Test Suite">
            <!-- ... -->
            <directory>../src/*/*Bundle/MoreTests</directory>
        </testsuite>
    </testsuites>
</phpunit>

I was looking for a way to run a console command right before any test was run. Looking through the configuration options for phpunit.xml, I found a way to run a "bootstrap" PHP file. You can point to this bootstrap file by setting the "bootstrap" of the root element in phpunit.xml:

<phpunit bootstrap="my_phpunit_bootstrap.php">

The funny thing is, Symfony2 already uses this "bootstrap" attribute to load the most important files of the framework before the tests are run. So, when you want to provide your own bootstrap, make sure to include Symfony2's bootstrap file first:

// in /app/my_phpunit_bootstrap.php:
require_once __DIR__.'/bootstrap.php.cache';

Let's see if we can get some console commands to run from within this bootstrap file. For example, we might want to drop the test database and recreate it. We can accomplish this, by adding the following code to my_phpunit_bootstrap.php.

But first set a different database name for the "test" environment. You can do this by adding these lines to /app/config/config_test.yml:

doctrine:
    dbal:
        # add "_test" to the database name you set in parameters.ini:
        dbname:   %database_name%_test

Now, when you have a dedicated database for your test situation, put this in bootstrap.php:

require_once __DIR__.'/bootstrap.php.cache';
require_once __DIR__.'/AppKernel.php';

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Input\ArrayInput;

use Symfony\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Symfony\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;

$kernel = new AppKernel('test', true); // create a "test" kernel
$kernel->boot();

$application = new Application($kernel);

// add the database:drop command to the application and run it
$command = new DropDatabaseDoctrineCommand();
$application->add($command);
$input = new ArrayInput(array(
    'command' => 'doctrine:database:drop',
    '--force' => true,
));
$command->run($input, new ConsoleOutput());

// add the database:create command to the application and run it
$command = new CreateDatabaseDoctrineCommand();
$application->add($command);
$input = new ArrayInput(array(
    'command' => 'doctrine:database:create',
));
$command->run($input, new ConsoleOutput());

Of course, after dropping and creating the database, the last thing we need is to automatically create table. Add an extra use statement:

use Symfony\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;

At the end of the file, add these lines:

// let Doctrine create the database schema (i.e. the tables)
$command = new CreateSchemaDoctrineCommand();
$application->add($command);
$input = new ArrayInput(array(
    'command' => 'doctrine:schema:create',
));
$command->run($input, new ConsoleOutput());

Now we have a nice PHPUnit bootstrap that recreates the database and all tables on every test run.

Important addition

Please note: some problems occurred when using the solution described above. After quite some debugging, I found the solution (see my comment below).

PHP Testing Symfony2 configuration PHPUnit console
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Konstantin Starojitski

Since you always recreate database you might as well use sqLite in memory.

// config_test.yml
doctrine:
....dbal:
........driver: pdo_sqlite
........# Path is mutually exclusive with memory. path takes precedence.
........# path: '%kernel.project_dir%/app/sqlite.db'
........memory: true
........charset: UTF8
// end of file

In this case all you need is this single command:

// bootstrap.php
$application = new Application($kernel);

$command = new UpdateSchemaDoctrineCommand();
$application->add($command);
$input = new ArrayInput(array(
'--force' => true
));

$command->run($input, new ConsoleOutput());
// end of file

This will also make your tests run much faster

Ahmed EBEN HASSINE

Could you explain the benifits of in_memory option please ?

Ante

Great post.

Here are new locations of requirements:

use Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;

Note that those are not under Symfony but in Doctrine root namespace

Ante

Also you need to require app/autoload.php instead of app/bootstrap.php.cache

Rheenendal

Awesome post. Thanks man, much appreciated.

Sergio

Hello folks. I was facing the following error:
PHP Fatal error: Class 'Symfony\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand' not found

I realize that it was moved to:
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;

Geeker Zhou

help a lot thanks

Krysh nar

Use it in a console command :

$command = $this->getApplication()->find('doctrine:database:drop');
$arguments = array(
'command' => 'doctrine:database:drop',
'--force' => true
);

$input = new ArrayInput($arguments);

try {
$command->run($input, $output);
} catch (\Exception $e) {
$output->writeln(sprintf('%s', $e->getMessage()));
}

$connection = $this->getContainer()->get('doctrine')->getConnection($input->getOption('connection'));
$connection->close();

$command = $this->getApplication()->find('doctrine:schema:create');
...

Stuart Grimshaw

Hi Matthias, I've been looking to get isolated unit tests for a while now in Symfony & I found your article while researching phpunit's bootstrap as a possible solution, which saved me quite some time.

I've also added the following to the end of my bootstrap to run my migrations & load my fixtures, https://gist.github.com/Stu...

Florent

This is exactly what I looking for. Thanx !

By the way your blog roxxx,
It's so rare to find many good articles about symfony2 in one place.

Matthias Noback

Thanks!

The Whole Life to Learn

Thanks a lot Matthias for this bootstrap.
But since I setted up this bootstrap, my tests don't run as before. During my functional tests, adding an entity isn't possible any more. After submitting the form, the request is no more redirected.
When I comment the commands, the tests work again fine.

Does someone know why it does so?

Matthias Noback

I have no idea, are you using Symfony 2.1? If so, things may have changed since I wrote this.

The Whole Life to Learn

I use Symfony 2.0.5 . I added some few lines at the end of the bootstrap, to populate my database so I have the minimum objects required:


if( file_exists(__DIR__.'/populate.sql') ) {
$sqlLines = file(__DIR__.'/populate.sql');
$command = new RunSqlDoctrineCommand();
$application->add($command);
foreach($sqlLines as $sqlLine) {
$input = new ArrayInput(array(
'command' => 'doctrine:query:sql',
'sql' => $sqlLine,
));
$command->run($input, new ConsoleOutput());
}
}

The Whole Life to Learn

I have resolved the problem. It came from PostgreSQL. PostgreSQL lost track of the ID's given in each row and so throws an error because of duplicated ID.

Bernat LabarĂ­as

Thanks for your contribution, seems there is very few documentation about running existant console commands. I have tested this and seems that works fine, but i still have some issues whilst running some console commands which have mandatory arguments (e.g. "doctrine:generate:entities name"). Do you know how to pass that mandatory arguments to the Application::run() method?

Matthias Noback

You can modify the first argument of InputArray and provide arguments by their name, and options by their name and a "--" prefix:

[php]
$input = new ArrayInput(array(
'command' => 'doctrine:generate:entities',
'name' => 'SomeName',
'--path' => '/thePath',
));
[/php]

Bernat LabarĂ­as

Many thanks Matthias, in fact I had already tested that by adding the argument to the ArrayInput(), but it was giving me some problems and I wasn't sure it was the proper way to do that. Now that you have given me this solution I know that the problems must be somewhere else.

Lukas

you may also want to check out:
https://github.com/liip/Lii...

this will allow you to load different fixtures for different tests

Matthias Noback

Seems like a really good idea! Thanks for your suggestion.

Ulrich Schmidt-Goertz

Hello Matthias,

I am experiencing the same issue as the commentators above, and I believe I have tracked it down. It looks like the doctrine:schema:create command does not open a new database connection, but re-uses an already open connection that has no database selected. According to my debugging, the database name was read from the config file correctly, but became unset during or after doctrine:database:drop.

As a Symfony/Doctrine novice, I don't have a quick solution at hand. Any hints would be greatly appreciated.

Matthias Noback

Finally I've found the solution! There were some problems with running the Doctrine commands all in a row. These problems don't occur when the commands are run seperately from the command line. In /vendor/symfony/src/Symfony/Bundle/DoctrineBundle/Command/DropDatabaseDoctrineCommand.php at the end of the execute() method, add

[php]
$connection->close();
[/php]

I will later put these additions in a pull request, but for now, this will do the trick!

Ulrich Schmidt-Goertz

It certainly does. Thanks a lot!

Matthias Noback

Only later I thought: maybe it's not that convenient to patch Symfony files manually. ;) Since the problem is that the connection is not closed, you may add these lines after running the DropDatabaseDoctrineCommand:

[php]
$connection = $application->getKernel()->getContainer()->get('doctrine')->getConnection();
if ($connection->isConnected()) {
$connection->close();
}
[/php]

This also does the trick.

Hugo

I also do have exacly the same problems.
It would be very cool if you could find it out.

in config_test.yml I have this:
doctrine:
dbal:
driver: %database_driver%
host: %database_host%
port: %database_port%
dbname: %database_name%_test
user: %database_user%_test
password: %database_password%_test
charset: UTF8

and I get this output:
Nothing to update - your database is already in sync with the current entity metadata.
Dropped database for connection named abacchus_test
Created database for connection named abacchus_test
ATTENTION: This operation should not be executed in a production environment.
Creating database schema...
PHPUnit 3.6.10 by Sebastian Bergmann.
SQLSTATE[3D000]: Invalid catalog name: 1046 No database selected

seems that doctrine:schema:create does not work?

Dustin Yoder

I am getting the following output when I run your code from your post using 'php -c app/'

Dropped database for connection named test
Created database for connection named test
ATTENTION: This operation should not be executed in a production environment.

Creating database schema...
PHPUnit 3.6.4 by Sebastian Bergmann.

SQLSTATE[3D000]: Invalid catalog name: 1046 No database selected

Matthias Noback

Hi Dustin, you will find the solution below...

Matthias Noback

I'm not sure what this means, I will try to reproduce this later and let you know if I found any solution.

Log0

I really appreciate your help! Thank you.

Log0

Thanks for the code, it moved forward a lot but I am still having issues.

37 // let Doctrine create the database schema (i.e. the tables)
38 $command = new CreateSchemaDoctrineCommand();
39 $application->add($command);
40 $input = new ArrayInput(array(
41 'command' => 'doctrine:schema:create',
42 ));
43 $command->run($input, new ConsoleOutput());

This piece of code throws error :

SQLSTATE[3D000]: Invalid catalog name: 1046 No database selected

Despite I have set up the AppKernel as test. Any clues?

Matthias Noback

See below for the solution!

Matthias Noback

I don't know what could be wrong; are you sure the parameter "database_name" is set in parameters.ini?

cordoval

to me it sounds strange the requirement:

$command->setContainer($kernel->getContainer());

don't understand how you thought it was needed...

interesting article

Matthias Noback

Thanks for your reply; you are certainly right, this was some old bit of code that got lost here. I removed it and better yet, also changed some other lines in the code above.