Writing Tests can be a very helpful tool to improve code quality. Especially when we refactor code over time. If you are able to establish a good core of tests, you will gain a lot of trust in your code.
Symfony is a very popular framework in the PHP ecosystem. It is often combined with the Doctrine Library to enable access to a database. Sometimes we need to add tests that depend on the Domain Entities. i.e. by functional test an API endpoint. This way we can ensure that the resulting JSON-data - returned by our application - is as expected. One use case could be as well to test filters of a resource list.
Of course we don't want to pollute our local development database with test data. Hence, we need a local test DB.
To configure a test database, we need to learn first how PHPUnit is set-up. The configuration file is called phpunit.xml.dist. In this file we can set a bootstrap file, a php script which will be executend when phpunit is run. Let's open / create this file and add the attribute "bootstrap" to the main phpunit tag:
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="false"
>
</phpunit>
The bootstrap tag expects a string which results in a file path to a php script. We can add "tests/bootstrap.php" as the value and save the phpunit.xml.dist file. From now on, each time we run phpunit, this script will be executed before the tests run.
With the configuration in place we can now create the bootstrap.php file and save it in our tests folder. As a content we can copy the basic setup for a Symfony applicatoin to this file:
<?php
use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__).'/vendor/autoload.php';
if (method_exists(Dotenv::class, 'bootEnv')) {
new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
( }
The content tells the bootstrap file to load a local environment file if the Application is supporting this and an .env file is present. Let's execute phpunit with our fresh configuration. You should see that more or less nothing has changed.
As for now, our application tests are still using the local development database. If we start to manipulate data in the database in our tests the changes would appear in our development database. This could lead to some unexpected results. For instance, if you add 100 new entities each time you run a test, your database will grow pretty fast and will contain a lot of useless data. Usually this is not what we want in a development database.
To switch from a development database to a Symfony test database connection in tests, we need to add a .env.test file (according to our environment, which is called test). In this file we can overwrite the DATABASE_URL entry:
DATABASE_URL="postgresql://localuser:localpass@database:5432/mydatabase_test"
You will need to adapt this line to the credentials that you already configured for the development database. Usually you can copy this line from the .env file and just append a "_test" at the end of the string.
We now achieved a very useful point. By running the phpunit tests our development database is not polluted anymore. This is good!
The bad news, our unit tests that depend on a Symfony test database connection will fail. Since we are referencing a test database that does not exist.
To solve the failing tests we can return to the bootstrap.php file. This file gives us the opportunity to execute code before running the code. This is a good place to create a database and load our database schema:
<?php
$env = 'test';
passthru(
sprintf(
'php bin/console doctrine:database:drop --if-exists --force --env=%s',
$env
);
)
passthru(
sprintf(
'php bin/console doctrine:database:create --if-not-exists --env=%s',
$env
);
)
passthru(
sprintf(
'php bin/console doctrine:schema:create --env=%s',
$env
); )
In the bootstrap.php script we now call three console commands. Since we know that we use a test database (configured before) we can drop any existing test database. This is achieved by the following Symfony command from the doctrine library:
php bin/console doctrine:database:drop --if-exists --force --env=%s
After this command finishes we can start over and create the database again, which is achieved by the following command:
php bin/console doctrine:database:create --if-not-exists --env=%s
This will ensure that we have a test database up and running. What we finally need to do is to create the schema for our entities. This will be our third and last command in this script:
php bin/console doctrine:schema:create --env=%s
This command will have a look at our Entity annotations / configurations and create the according tables and keys. A shiny new, empty database! With this setup we can run phpunit once again and see that our tests pass again, at least if they don't depend on specific entities. Congratulations!
In the next article I will describe how we can populate the database by Loading Fixtures in Symfony.
Thank you for reading this far! Let’s connect. You can @ me on X (@debilofant) with comments, or feel free to follow. Please like/share this article so that it reaches others as well.