Symfony2 & TDD: Testing a Configuration Class

Posted on by Matthias Noback

Some of you may already know: I am a big fan of TDD - the real TDD, where you start with a failing test, and only write production code to make that specific test pass (there are three rules concerning this actually).

The TDD approach usually works pretty well for most classes in a project. But there are also classes which are somewhat difficult to test, like your bundle's extension class, or its configuration class. The usual approach is to skip these classes, or to test them only using integration tests, when they are used in the context of a running application. This means you won't have all the advantages of unit tests for these "units of code". Some of the execution paths of the code in these classes may never be executed in a test environment, so errors will be reported when the code is running in production already.

Luckily, these kind of classes can be tested very well, but some tools were missing. Since it's difficult to remember the setup for extension and configuration classes (and compiler passes for that matter), you should be able to rely on some base classes for your unit tests. And you will also need some simple assertions for verifying the correctness of your code.

In the past few weeks I've spent some time to create these base classes for PHPUnit test cases, and some useful assertions too. In this article series I demonstrate how you can use them, to test these hard-to-test classes or seemingly untestable classes after all.

Configuration classes

The first thing you may want to test using unit tests is a bundle's configuration class. Such a class looks like this (though usually it is much bigger):

namespace Matthias\ApiBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();

        $rootNode = $treeBuilder->root('api');
        $rootNode
            ->isRequired()
            ->children()
                ->scalarNode('key')
                    ->isRequired()
                ->end()
            ->end();

        return $treeBuilder;
    }
}

First install matthiasnoback/SymfonyConfigTest. This will give you the AbstractConfigurationTestCase. You can use it to write tests for any configuration class. First, let's assert that a given set of (incorrect) values would result in an InvalidConfigurationException being thrown. We also want to make sure that the exception message contains the word "key" since that value is missing:

namespace Matthias\ApiBundle\Tests\DependencyInjection;

use Matthias\SymfonyConfigTest\PhpUnit\AbstractConfigurationTestCase;
use Matthias\ApiBundle\DependencyInjection\Configuration;

class ConfigurationTest extends AbstractConfigurationTestCase
{
    protected function getConfiguration()
    {
        return new Configuration();
    }

    /**
     * @test
     */
    public function the_api_key_value_is_required()
    {
        $this->assertConfigurationIsInvalid(
            array(
                array() // no configuration values at all
            ),
            'key' // exception message should contain "key"
        );
    }
}

Please note that you have to provide an array of arrays as the first argument of this assertion. The reason is that the configuration processor merges configuration values while processing them, and you may sometimes want to test the merging process too (since you can influence it). Like in the test below, where an assertion is used to validate the resulting configuration values, after processing the given raw input arrays:

class ConfigurationTest extends AbstractConfigurationTestCase
{
    /**
     * @test
     */
    public function it_keeps_the_last_provided_api_key()
    {
        $value = 'some value';

        $this->assertProcessedConfigurationEquals(
            array(
                array('key' => 'this value will be overwritten'),
                array('key' => $value)
            ),
            array(
                'key'=> $value
            )
        );
    }
}

This test verifies that the last provided value for "key" wins.

Next up: testing an extension class

The next post will be about testing your bundle's extension class.

PHP Symfony2 Testing bundle configuration TDD