Running PHPUnit tests before git push

What usually happens

“Great, I’ve just finished this new feature and it works perfectly, let’s merge it with the rest of the code so the team can now benefit from it..”

git add .
git commit -m "-blablabla"
git flow feature finish "blabla-feature-name"
git push origin develop

“Oops, I forgot to pass tests after closing the feature branch…”

./vendor/bin/simple-phpunit

...F
FAILURES!
Tests:4... Failures: 1

We’ve all done it before, pushed code that didn’t pass tests…

Pre-Push git hook solution

On this blog post I’ll show you how to set a pre-push script, so every time you push code to your remote repository (develop or master only for this example), it’ll try to run your tests. And as a result, it will only push the code if tests pass.

This is intended to be like a safety net, specially if you’re working with a team and your code needs to be tested before being pushed!

This is what the script file looks like:

#!/usr/bin/env php

< ?php

/**
 * ===========================
 * This hook will only allow git push if your all test pass
 * To activate this hook, you need to place this file in the .git/hooks folder
 * ===========================
 **/


$gitBranch = shell_exec('git branch | grep \*');
$branchName = trim(str_replace('*', '', $gitBranch));
if( $branchName !== 'develop' && $branchName !== 'master' ) {
    echo "You're not in master or develop, so hook tests won't activate.." . PHP_EOL;
    exit(0);
}

echo "Running tests.. ";
exec('./vendor/bin/simple-phpunit', $output, $returnCode);
if ($returnCode !== 0) {
  // Show full output
  echo PHP_EOL . implode($output, PHP_EOL) . PHP_EOL;
  echo "Cannot push changes untill tests are OK.." . PHP_EOL;
  exit(1);
}

// Show summary (last line)
echo array_pop($output) . PHP_EOL;
exit(0);
Note1: Notice the start of script tag < ?php . You will need to remove the white space so PHP interprets the code.
Note2: The name of this file must be pre-push and must be located inside the .git/hooks folder in your project. This is because git will look for that actual file before pushing.

So if we dive into the code, it's pretty simple.

The first block looks in what branch you're located. I've just set up this script to run tests when you try and push to develop or master. You can modify/delete this part if you want a different behaviour.

The second block is the actual running of the tests. This only runs if you're located in develop or master.

As you can see, if return code after running tests is different than 0, AKA a tests failed, it'll exit the script with an exit(1), letting git know it must not push the code.

If all test pass, it'll exit with an exit(0) and so, the code will be pushed.

And that's it! Simple right?

There are other git hooks that might be useful for you. Try looking at the .git/hooks folder for samples (pre-commit, pre-rebase, pre-receive...) and let me know if you use any other ones!

Cheers and safe coding! ­čÖé

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

Now run the phpunit command to run tests.

cd my_project
# if you have the phpunit phar installed globally
phpunit tests/
# if you use the phar file directly
php phpunit.phar tests/

Right off the bat, it’ll give you an error…

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()

This problem is due to an incompatibility with code dependencies from PHPUnit and Symfony 3.2+ code, specifically with the Yaml component.

Don’t panic though, PHPUnit is working to solve this issue.

More info on the PHPUnit issue here.
More info on Symfony’s explanation here.

In the meantime, we can use the PHPUnit bridge to test our application.

cd my_project
composer require --dev symfony/phpunit-bridge

Once your composer json and lock have updated, it’s time to run the test!

./vendor/bin/simple-phpunit

It works!!

Step 2 –┬áInstalling doctrine’s fixtures bundle.

First run the composer require

composer require --dev doctrine/doctrine-fixtures-bundle

Once your composer json and lock have updated, it’s time to install it in our Kernel.

// app/AppKernel.php

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

Now it’s installed, let’s create our first fixture. Let’s suposse we installed FOSUserBundle to manage our users, so for instance we could create a couple users like so:

// 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);
        }
    }
}

Now if we run the fixtures like so

php bin/console doctrine:fixtures:load --append

We’ll get another error:

In LoadDataFixturesDoctrineCommand.php line 95:
                                                
  [InvalidArgumentException]                    
  Could not find any fixture services to load. 

To fix this, we’ll need to add this to services.yml

# services.yml
services:
    # ...
    AppBundle\DataFixtures\:
        resource: '../../src/AppBundle/DataFixtures'
        tags: ['doctrine.fixture.orm']

Yes, you guessed it, the magic here relies on the tag. Doctrine will load all services that have that ‘doctrine.fixture.orm’ tag. On the other hand, it’ll also load all the classes that implement the ORMFixtureInterface (for instance, those classes extending the Fixture class). More info on all of this in the DoctrineFixtureBundle doc page.

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%"

This will point to the new test database.

Then add the data in parameters.yml (don’t forget parameters.yml.dist for future installs)

# 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
    }
}

This will create the database and install the fixtures on every test that extends this TestCase. This is important as you want a consistent database with the same data every time!

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.php

class 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));
    }

  
}

Now you just need to run the phpunit command like so

./vendor/bin/simple-phpunit

I’ve created a bash wrapper script called ‘test’ that contains just this line of code so now I just need to run

bash test

Happy coding and testing! ­čÖé

Symfony2 PHPUnit testing database data

Today I had to test some complex algorithm logic we’re writing for a client app. The algorithm I want to test interacts with the DDBB several times through query builders and repositories, so it’s necessary to test this database access, as we need to know the sql queries give the right data also.

It’s the first time I set up a test environment (I worked with Unit Testing before, but never set up the env from scratch). So after reading a bit of doc from the internet,┬áI set up the unit test environment, then wrote some tests that interacted with a test database and test fixtures.

I’ve put together a bit of guide for anyone wanting to get started on this.

Step 1 –┬áInstalling doctrine’s fixtures bundle.

First run the composer require

composer require --dev doctrine/doctrine-fixtures-bundle

Once your composer json and lock have updated, it’s time to install it in our Kernel.

// app/AppKernel.php

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

Step 2 –┬á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%"
        # Workaround for DBAL 2.5 auto-detect -> with server_version allows to doctrine:database:create
        server_version: 5.6 # your database server version here

This will point to the new test database.

Then add the data in parameters.yml (don’t forget parameters.yml.dist for future installs)


# This file is auto-generated during the composer install
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 3 – 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.


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 --fixtures=tests/AppBundle/DataFixtures/ORM');

        $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
    }
}

Most of the code is taken from StackOverflow.

This will create the database and install the fixtures on every test that extends this TestCase. This is important as you want a consistent database with the same data every time!

It’s also important to pass the parameter

--fixtures=tests/AppBundle/DataFixtures/ORM'

This way you can set up all your test fixtures separate from your app fixtures, which will stay clean.

Step 4 – 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.

class FooTest extends DataFixtureTestCase
{
    protected $fooService;

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

    public function testFooTrue()
    {

        ... 
        //custom logic
        ... 

        $this->assertEquals(true, $value);
    }

  
}

Now you just need to run the phpunit command like so

phpunit tests/

And that’s all you need! Happy testing! ­čÖé

PS: This is a very easy way to get started. For more complex testing environment settings, you can try LiipFunctionalTestBundle