Symfony 3.4 PHPUnit testing database data

Hey guys, this post is a follow up from my last phpunit in Symfony2 post. This time I’ve had to install a test environment in a Symfony 3.4 project and I’ve had some weird problems, so I thought of writing this blog post once I solved them all, it may be helpful to someone out there :)

Step 1 — Installing a fresh Symfony 3.4 project.

In your symfony project, run the composer require

symfony new my_project 3.4
cd my_project
# if you have the phpunit phar installed globally
phpunit tests/
# if you use the phar file directly
php phpunit.phar tests/
PHPUnit 5.7.21 by Sebastian Bergmann and contributors.

E 1 / 1 (100%)

Time: 128 ms, Memory: 14.00MB

There was 1 error:

1) Tests\AppBundle\Controller\DefaultControllerTest::testIndex
Error: Call to undefined method Symfony\Component\Yaml\Parser::parseFile()
cd my_project
composer require --dev symfony/phpunit-bridge
./vendor/bin/simple-phpunit

Step 2 — Installing doctrine’s fixtures bundle.

First run the composer require

composer require --dev doctrine/doctrine-fixtures-bundle
// app/AppKernel.php

// ...
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
// ...
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
}
// AppBundle/DataFixtures/ORM/LoadUserData.php

namespace AppBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use AppBundle\Entity\User;

class LoadUserData implements FixtureInterface, ContainerAwareInterface
{
private $data = [
'ROLE_USER' => [
'username' => 'user',
'email' => 'user@slowcode.io',
'plainPassword' => 'user@slowcode.io'
],
'ROLE_ADMIN' => [
'username' => 'admin',
'email' => 'admin@slowcode.io',
'plainPassword' => 'admin@slowcode.io'
],
];

private $container;

public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}

public function load(ObjectManager $manager)
{
$userManager = $this->container->get('fos_user.user_manager');

foreach ($this->data as $role => $attrs) {
$user = $userManager->createUser();
foreach ($attrs as $attr => $val) {
$function = 'set'. ucwords($attr);
$user->$function($val);
$user->setEnabled(true);
}
$user->setRoles(['ROLE_USER', $role]);
$userManager->updateUser($user, true);
}
}
}
php bin/console doctrine:fixtures:load --append
In LoadDataFixturesDoctrineCommand.php line 95:

[InvalidArgumentException]
Could not find any fixture services to load.
# services.yml
services:
# ...
AppBundle\DataFixtures\:
resource: '../../src/AppBundle/DataFixtures'
tags: ['doctrine.fixture.orm']

Step 3 — Setting a separate database for testing.

Add these lines on your config_test.yml file.

# config_test.yml
# ...
# Doctrine Configuration
doctrine:
dbal:
driver: "%test_database_driver%"
host: "%test_database_host%"
port: "%test_database_port%"
dbname: "%test_database_name%"
user: "%test_database_user%"
password: "%test_database_password%"
# parameters.yml
parameters:
# ...
test_database_driver: pdo_mysql
test_database_host: localhost
test_database_port: null
test_database_name: test_db_name
test_database_user: test_db_user
test_database_password: test_db_pw

Step 4 — Creating a TestCase that we will extend when ddbb fixtures are needed.

This is useful for reusing code, instead of writing it on every file. It also makes all the fixture tests more consistent.

// tests/AppBundle/DataFixtures/DataFixtureTestCase.php
namespace Tests\AppBundle\DataFixtures;

use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;

class DataFixtureTestCase extends WebTestCase
{
/** @var Application $application */
protected static $application;

/** @var Client $client */
protected $client;

/** @var ContainerInterface $container */
protected $container;

/** @var EntityManager $entityManager */
protected $entityManager;

/**
* {@inheritDoc}
*/
public function setUp()
{
self::runCommand('doctrine:database:drop --force');
self::runCommand('doctrine:database:create');
self::runCommand('doctrine:schema:create');
self::runCommand('doctrine:fixtures:load --append --no-interaction');

$this->client = static::createClient();
$this->container = $this->client->getContainer();
$this->entityManager = $this->container->get('doctrine.orm.entity_manager');

parent::setUp();
}

protected static function runCommand($command)
{
$command = sprintf('%s --quiet', $command);

return self::getApplication()->run(new StringInput($command));
}

protected static function getApplication()
{
if (null === self::$application) {
$client = static::createClient();

self::$application = new Application($client->getKernel());
self::$application->setAutoExit(false);
}

return self::$application;
}

/**
* {@inheritDoc}
*/
protected function tearDown()
{
self::runCommand('doctrine:database:drop --force');

parent::tearDown();

$this->entityManager->close();
$this->entityManager = null; // avoid memory leaks
}
}

Step 5 — Creating the unit, functional and integration tests

After creating your test fixtures, now you can test your services logic easily by extending the TestCase we just created, like so.

// tests/AppBundle/User/FooTest.phpclass FooTest extends DataFixtureTestCase
{
protected $fooService;

/**
* {@inheritDoc}
*/
public function setUp()
{
parent::setUp();
$this->fooService = $this->container->get('app.service.foo');
}

public function test_get_two_users()
{
$users = $this->entityManager->getRepository(User::class)->findAll();
$this->assertEquals(2, count($users));
}


}
./vendor/bin/simple-phpunit
bash test

Coder, Entrepreneur, Co-founder at SlowCode