Silex: set up your project for testing with PHPUnit

Posted on by Matthias Noback

In my previous post I wrote down a set of requirements for Silex applications. There were a few things left for another post: first of all, I want to have unit tests, nicely organized in directories that correspond to the namespaces of my project's classes. This means that the tests for Acme\SomeNamespace\SomeClass should be found in Acme\Tests\SomeNamespace\SomeClass.

Organizing your unit tests

I want to write my tests for the PHPUnit framework. This allows me to use some PHPUnit best practices: first of all we define our PHPUnit configuration file in /app/phpunit.xml. First of all we point PHPUnit to a bootstrap file (of which we will later define it's contents). We also set an environment variable called "env" whose value is "test". Finally we set the location of our app.php file inside the server variable "env".

<!-- in /app/phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
         bootstrap="./../tests/bootstrap.php"
>
    <testsuites>
        <testsuite name="YourApp Test Suite">
            <directory>./../tests/</directory>
        </testsuite>
    </testsuites>

    <php>
        <server name="APP_DIR" value="/var/www/silexsandbox/app" />
        <env name="env" value="test" />
    </php>
</phpunit>

Your project's classes should be available both in the application and in your test suite. But the test classes should only be available when PHPUnit wants to run the tests. Therefore we need /app/bootstrap.php to register namespaces for shared classes, and then /tests/bootstrap.php should also register the test namespaces. Since both bootstrap files need an autoloader, we can reuse it using a return statement. /app/bootstrap.php should look like this:

// /app/bootstrap.php

require_once __DIR__.'/../vendor/silex.phar';

use Symfony\Component\ClassLoader\UniversalClassLoader;

$loader = new UniversalClassLoader();

$loader->registerNamespace('Acme', __DIR__.'/../src');

$loader->register();

return $loader;

Then, /tests/bootstrap catches the returned $loader and registers the namespace for our tests:

// in /tests/bootstrap.php

$loader = require_once __DIR__.'/../app/bootstrap.php';

$loader->registerNamespace('Acme\\Tests', __DIR__);

Inside the /tests directory we can create test classes, for example /tests/Acme/Tests/SomeNamespace/SomeClassTest.php. Make sure these classes extends \PHPUnit_Framework_TestCase.

Go the root of your project and run

phpunit -c app/

Or go to the /app directory and just run phpunit. You will see that all of your tests in /tests will be run.

Functional tests

For functional tests we need the entire Silex application. Silex provides a WebTestCase class which is perfect for this. To be able to use it, you should extend it and implement it's createApplication() method. This method should return your Silex application. Here we use the $_SERVER['APP_DIR'] that we set from within the PHPUnit configuration file.

namespace Acme\Test;

use Silex\WebTestCase as BaseWebTestCase;

class WebTestCase extends BaseWebTestCase
{
    public function createApplication()
    {
        $app = require $this->getApplicationDir().'/app.php';

        return $app;
    }

    public function getApplicationDir()
    {
        return $_SERVER['APP_DIR'];
    }
}

When we extend a test class from this custom WebTestCase, the entire Silex application will be available from within each test method, since a call to createApplication() will me made for each test. See the Silex documentation for more information about functional testing, using createClient().

Using the "test" environment variable

In phpunit.xml we defined an environment variable called "env". This variable is available in our application as $_ENV['env']. Checking for it's value allows us for instance to connect to a different database for testing (this could for be for example an in-memory sqlite database):

// in /app/app.php

$app['env'] = $_ENV['env'] ?: 'dev';

if ('test' === $app['env']) {
   // run only in "test" environments
}
PHP Testing Silex PHPUnit