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).
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
Could you explain the benifits of in_memory option please ?
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
Also you need to require app/autoload.php instead of app/bootstrap.php.cache
Awesome post. Thanks man, much appreciated.
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;
help a lot thanks
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');
...
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...
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.
Thanks!
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?
I have no idea, are you using Symfony 2.1? If so, things may have changed since I wrote this.
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());
}
}
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.
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?
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]
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.
you may also want to check out:
https://github.com/liip/Lii...
this will allow you to load different fixtures for different tests
Seems like a really good idea! Thanks for your suggestion.
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.
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 theexecute()
method, add[php]
$connection->close();
[/php]
I will later put these additions in a pull request, but for now, this will do the trick!
It certainly does. Thanks a lot!
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.
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?
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
Hi Dustin, you will find the solution below...
I'm not sure what this means, I will try to reproduce this later and let you know if I found any solution.
I really appreciate your help! Thank you.
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?
See below for the solution!
I don't know what could be wrong; are you sure the parameter "database_name" is set in parameters.ini?
to me it sounds strange the requirement:
$command->setContainer($kernel->getContainer());
don't understand how you thought it was needed...
interesting article
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.