Symfony2 - Quick functional tests 15/11/2014
TL;DR: Only check the status and exit code, don't use the given
TestCase
.
Provided that your controllers and commands are thin and they rely on services which are heavily unit tested, only checking the status and exit code in your functional test should be entirely sufficient.
Note: Checking at least the status and exit code is recommended by Symfony's Official Best Practices.
In this article, we will see how easy and quick it is to write them.
Making the Kernel available
If you're familiar with Symfony2, you might use one of
the given KernelTestCase
to write your tests with PHPUnit.
The whole purpose of this file is to create an instance of the application's Kernel, by guessing its localization. The problem with this approach is that it ties you to the PHPUnit test framework. If you have a look at its code, you'll also find it a bit complicated.
Note:
WebTestCase
also makes available a crawler, which we don't need as we only intend on checking the status code, not the body.
Let's take an easier way: we will create a bootstrap file which requires the kernel's file:
<?php
// File: app/bootstrap.php
require __DIR__.'/bootstrap.php.cache';
require __DIR__.'/AppKernel.php';
Now all you need to do for your tests is to use this file. For example with PHPUnit:
<?xml version="1.0" encoding="UTF-8"?>
<!-- File: app/phpunit.xml.dist -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.3/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="./bootstrap.php"
>
<testsuites>
<testsuite name="Test Suite">
<directory>./src/AppBundle/Tests</directory>
</testsuite>
</testsuites>
</phpunit>
Testing commands
Now let's say we're testing the famous AcmeDemoBundle, and its hello world command:
<?php
namespace Acme\DemoBundle\Tests\Command;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Input\ArrayInput;
class HelloWorldCommandTest extends \PHPUnit_Framework_TestCase
{
private $app;
private $output;
protected function setUp()
{
$kernel = new \AppKernel('test', false);
$this->app = new Application($kernel);
$this->app->setAutoExit(false);
$this->output = new NullOutput();
}
public function testItRunsSuccessfully()
{
$input = new ArrayInput(array(
'commandName' => 'acme:hello',
'name' => 'Igor',
));
$exitCode = $this->app->run($input, $this->output);
$this->assertSame(0, $exitCode);
}
}
As you can see our test is neatly structured in 3 parts: input definition, the actual call and finally the check.
Note: the
setAutoExit
method will ensure that the application doesn't call PHP'sexit
. TheNullOutput
ensures that nothing is displayed.
Testing controllers
Once again let's test AcmeDemoBundle, this time the demo controller:
<?php
namespace Acme\DemoBundle\Tests\Controller;
use Symfony\Component\HttpFoundation\Request;
class DemoControllerTest extends \PHPUnit_Framework_TestCase
{
private $app;
protected function setUp()
{
$this->app = new \AppKernel('test', false);
$this->app->boot();
}
public function testHomepage()
{
$request = new Request::create('/', 'GET');
$response = $this->app->handle($request);
$this->assertTrue($response->isSuccessful());
}
public function testItSaysHello()
{
$request = new Request('/hello/igor', 'GET');
$response = $this->app->handle($request);
$this->assertTrue($response->isSuccessful());
}
public function testItSendsEmail()
{
$request = new Request('/contact', 'POST', array(
'email' => 'igor@example.com',
'content' => 'Hello',
));
$response = $this->app->handle($request);
$this->assertTrue($response->isSuccessful());
}
}
Note: The
boot
method makes the container available.
Conclusion
We stripped Symfony2 to its bare minimum and as a result we are now able to write functional tests without any effort.
I hope you enjoyed this article, please feel free to tweet me for any comment and question.