Setting up a testing database in Symfony

Soufiyane Aitmoulay
4 min readJul 7, 2023

--

Setting up a testing database in Symfony can be challenging, I’ve encountered the same difficulties and decided to write a quick guide to help you through the process. By following these steps, you won’t have to go through the same hassle. Before we begin, there are a few bundles we need to add to our project.
First, let’s install the “liip/test-fixtures-bundle” bundle, which provides base classes for functional tests and assists in setting up test databases and loading fixtures

composer require --dev liip/test-fixtures-bundle

This bundle is compatible with any database supported by Doctrine ORM, such as MySQL, PostgreSQL, SQLite, etc. If you’re using MongoDB, you should use the “DoctrineMongoDBBundle” instead.
Next, let’s install the “orm-fixtures” bundle, which is necessary for loading fixtures into the database.

composer require --dev orm-fixtures

Assuming you have already installed PHPUnit bundle and Doctrine, we can proceed to set up our testing database. Open the “.env.test” file and add the following code to define the environment and the database URL

APP_ENV=test
DATABASE_URL="mysql://username:password@127.0.0.1:3306/dbname?serverVersion=8&charset=utf8mb4"

Now, we can create our testing database by running the following commands

symfony console doctrine:database:create --env=test
symfony console doctrine:schema:update --env=test --force

Check your PHPMyAdmin to verify if the testing database has been created successfully. Now, let me show you how to perform database testing with and without fixtures.
Before we proceed, make sure to update your “doctrine.yml” file in the config directory to include your testing database name:

when@test:
doctrine:
dbal:
dbname: 'dbname_test'

Let’s start with our first test, which aims to ensure that our product entity is inserted into the database correctly.

class ProductTest extends KernelTestCase
{

public function testProductIsInsertedSuccessfully() : void{

self::bootKernel();
$entityManager = static::$kernel->getContainer()
->get('doctrine')
->getManager();

$product = new Product();
$product->setName('Test Product');
$product->setPrice(10.99);
$product->setSlug('new_slug');

$entityManager->persist($product);
$entityManager->flush();

$insertedProduct = $entityManager->getRepository(Product::class)->findOneBy([
'name' => 'Test Product',
]);

self::assertNotNull($insertedProduct);
self::assertEquals('Test Product', $insertedProduct->getName());
self::assertEquals(10.99, $insertedProduct->getPrice());

}
}

Here’s a breakdown of what the code do :

The class ProductTest extends KernelTestCase, which is a base class provided by Symfony for testing purposes.

  1. The testProductIsInsertedSuccessfully method is a test case that verifies the successful insertion of a product.
  2. self::bootKernel() boots up the Symfony kernel for the test environment.
  3. $entityManager is obtained from the container, which is responsible for managing the entities and performing database operations.
  4. A new Product object is created, and its properties (name, price, and slug) are set.
  5. $entityManager->persist($product) is used to tell the entity manager to manage the Product entity and prepare it for insertion into the database.
  6. $entityManager->flush() flushes all the pending changes to the database, including the insertion of the product.
  7. $entityManager->getRepository(Product::class)->findOneBy() is used to retrieve the inserted product from the database based on the specified criteria (name in this case).
  8. Several assertions are made using self::assertNotNull and self::assertEquals to check if the inserted product exists and if its properties match the expected values.

Now, let’s move on to testing the database with fixtures. We’ll create a fixture class to load a sample product into the database

<?php

namespace App\DataFixtures;

use App\Entity\Product;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

class ProductFixtures extends Fixture
{

public function load(ObjectManager $manager)
{

$product = new Product();
$product->setName('product 1');
$product->setPrice(40);
$product->setSlug('product/slug/1');
$manager->persist($product);
$manager->flush();

}
}

This simple fixture class inserts one product into our database. To load fixtures into the testing database, run the following command:

symfony console doctrine:fixtures:load --env=test

Whether you run the command or not, the fixtures will be loaded in every test.

However, before proceeding, we need to install another bundle called “dama/doctrine-test-bundle.” This bundle ensures that data is restored before every test, which is considered a best practice. Install it by running

composer require --dev dama/doctrine-test-bundle

To enable the bundle, open the “phpunit.xml.dist” file and add the following code

  <extensions>
<extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
</extensions>

Now, we are ready to create our tests with fixtures

<?php

namespace App\Tests;

use App\DataFixtures\ProductFixtures;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Liip\TestFixturesBundle\Services\DatabaseToolCollection;
use Liip\TestFixturesBundle\Services\DatabaseTools\AbstractDatabaseTool;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;


class ProductFixtureTest extends KernelTestCase
{
protected AbstractDatabaseTool $databaseTool;
protected EntityManagerInterface $entityManager;

public function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->entityManager = static::$kernel->getContainer()->get('doctrine')->getManager();
$this->databaseTool = static::$kernel->getContainer()->get(DatabaseToolCollection::class)->get();
}

public function testDeleteProduct(): void
{
$this->databaseTool->loadFixtures([
ProductFixtures::class
]);

$insertedProduct = $this->entityManager->getRepository(Product::class)->findOneBy([
'slug' => 'product/slug/1',
]);
$this->entityManager->getRepository(Product::class)->remove($insertedProduct,true);
$deletedProduct = $this->entityManager->getRepository(Product::class)->findOneBy([
'slug' => 'product/slug/1',
]);
$this->assertNull($deletedProduct);

}
public function testProductExist(): void
{
$this->databaseTool->loadFixtures([
ProductFixtures::class
]);

$insertedProduct = $this->entityManager->getRepository(Product::class)->findOneBy([
'slug' => 'product/slug/1',
]);
self::assertNotNull($insertedProduct);
}


}

The setUp() method is a special method in PHPUnit that is executed before each test method. In this method, the parent's setUp() method is called first, which sets up the test environment

The testDeleteProduct() method tests the deletion of a product. First, the loadFixtures() method from the database tool is called to load the necessary fixtures (data) for the test, in this case, the ProductFixtures class. Then, the entity manager is used to find a product with a specific slug ('product/slug/1'). The remove() method is called on the repository to delete the found product. Next, the entity manager is used again to try to find the deleted product. Finally, the assertNull() method is used to assert that the deleted product is null, indicating that it was successfully deleted

The testProductExist() method tests if a product exists. Similarly to the previous test, fixtures are loaded using the loadFixtures() method. The entity manager is then used to find a product with the specified slug. The assertNotNull() method is used to assert that the product is not null, indicating that it exists, which means that data is restored before every test is running.

I hope this guide helps you! Good luck with your testing endeavors.

--

--

Soufiyane Aitmoulay

Hello, my name is Soufiyane AitMoulay, I'm a junior full-stack web developer, currently finishing my engineering studies in computer science.