Blogpost

Passage of time

3 minute read

PHPUnit tests for symfony

How to setup PHPUnit testing to test your symfony application

PHPUnit tests are used to test isolated pieces of code in your application. At DeltaBlue we did some brainstorming on this subject and came to the following conclusions:

  • As we have a gigantic codebase, including microservices, it’s impractical to test single PHP classes.
  • Therefore we better focus on Isolated Action testing, meaning we will test the action calls.
  • Focussing on action calls, we can organise the tests by Controller.

To setup PHPUnit testing, we alter phpunit.xml.dist to the following:

<?xml version="1.0" encoding="UTF-8"?>

<!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
         backupGlobals="false"
         colors="true"
         stopOnFailure="true"
         bootstrap="tests/bootstrap.php"
>
    <php>
        <ini name="error_reporting" value="-1" />
        <server name="KERNEL_DIR" value="app/" />
        <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
        <env name="SYMFONY_ENV" value="test" />
    </php>

    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>src</directory>
            <exclude>
                <directory>src/*Bundle/Resources</directory>
                <directory>src/*/*Bundle/Resources</directory>
                <directory>src/*/Bundle/*Bundle/Resources</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>

What’s to be noticed here? Tests will be stored in the tests directory ( under root ), the SYMFONY_ENV parameter is set to test, which will call config_test.yml when bootstrapping AND we will use a custom bootstrap for testing.

That custom bootstrap looks like :

<?php

passthru('php '. __DIR__. '/../bin/console ca:cl');

passthru('php '. __DIR__. '/../bin/console doc:database:create');

passthru('php '. __DIR__. '/../bin/console doc:sch:up -f');

passthru('php '. __DIR__. '/../bin/console doctrine:fixtures:load -n', $result);
if ( $result != 0) {
    exit("Could not fully execute fixtures.\n");
}

require __DIR__.'/../app/autoload.php';

Before every test run, we will create a new database and load fixtures, so our tests always run on the same database. Best way to do this, is to setup sqlite for your testing.

Setup of sqlite is simply done in config_test.yml

imports:
    - { resource: config_dev.yml }

framework:
    test: ~
    session:
        storage_id: session.storage.mock_file
    profiler:
        collect: false

web_profiler:
    toolbar: false
    intercept_redirects: false

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   pdo_sqlite
                path:     "%kernel.cache_dir%/test.db"

services:
    some_awesome_service:
        class: TestBundle\Services\AwesomeService

This will create a little test.db file on each test run, which will contain your database. Tests will always run on the same dataset. This has some pros and cons, which you should discuss internally to decide if this is the best approach for your situation or not.

config_test.yml can also be used to override some services to use a mocking service during testing. This is awesome, because this means you can fully isolate your tests and e.g. replace a service that makes an external API call so it always returns the same output!


Now all that’s left to do is write your tests. If you have e.g. an action that lists all projects, and that action is the ListAction method in the MYBUNDLE/ProjectController, the corresponding test would be:

tests/MYBUNDLE/Controller/Project/ListActionTest

And could look something like:

<?php

namespace Tests\MYBUNDLE\Controller\Project;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ListActionTest extends WebTestCase
{
    
    public function getRoute()
    {
        return 'application.project.list';
    }
    
    public function testSuccesfull()
    {
        $client = $this->createClient(['test_case' => 'authorizedClient', 'root_config' => 'config.yml']);
        
        $client->request( 'GET', $this->getPath());
        $response = $client->getResponse();
        $this->assertEquals(200, $response->getStatusCode());
        return $response;
    }
    
    public function getPath()
    {
        $router = static::$kernel->getContainer()->get('router');
        return $router->generate($this->getRoute());
    }
    
}

So for every one of your Actions, add a unit test to check a successful call, an unauthorized call,… until every call is covered.