📜 ⬆️ ⬇️

Testing the controller in symfony2

I bring to your attention the translation of yesterday's post by one of the Symfony2 developers about the approach to unit testing of controllers in Symfony2. The theme is very relevant for Symfony2 developers. It is also worth noting that the post mentions the result of the discussion on dev-groups about using the controller as a service in Symfony2.


Even with a lot of experience working with MVC frameworks, one thing remains undisclosed all the time - how to test controllers. I think that the main reason for this is the non-obviousness of testing, since controllers belong to the sort of elements of the “black magic” framework. There are many agreements regarding the placement of controllers in the file system, which dependencies should be aware of, and which controller should have hard links (view layer).

This arrangement of things does not imply easy ways to test controllers until you can isolate the controller and some of its basic dependencies for interaction testing — you need to run the entire framework and run functional tests.
')
Since this process is rather complex and complex, people usually do not resort to unit-testing of controllers, functional tests are the maximum that you can get, but usually there is no testing at all.

Symfony2 completely changes things.

Initially, in the symfony2 framework, there is only an agreement on loading the controller. The controller instance remains very lightweight and does not require the expansion of a certain parent class for its work. If your controllers implement the ContainerAware interface, you will get a DIC (dependency injection container) implemented through the ContainerAware :: setContainer () method, which you can use to access any service that you declared in the DIC .

The recommended method of testing controllers for some time was an approximation of black box testing when you test full requests to an application and check the output like this:
<?php
$client = $ this ->createClient();

$client->request( 'GET' , '/index' );
$response = $client->getResponse();

$ this ->assertEquals(200, $response->getStatusCode());
$ this ->assertRegExp( '/<h1>My Cool Website<\/h1>/' , $response->getContent());
Despite the fact that this method is easy to read and understand, it has drawbacks:In a perfect world, I would like to test the interaction of the controller with other services in my application, like this:
<?php

namespace Company\ApplicationBundle\Tests\Controller;
use Company\ApplicationBundle\Controller\IndexController;

class IndexControllerTest extends \PHPUnit_Framework_TestCase
{
//...
public function testIndexAction()
{
$templating = $ this ->getMock( 'Symfony\Bundle\FrameworkBundle\Templating\Engine' );
$templating->expects($ this ->once())
->method( 'render' )
->with( 'Application:Index:index' )
->will($ this ->returnValue( 'success' ))
;

$controller = new IndexController();
$controller->setTemplating($templating);

$ this ->assertEquals( 'success' , $controller->indexAction());
}
}
Note: the controller is now a POPO (plain old PHP object) without a base class that it should extend. Symfony2 doesn’t need anything else to work except the controller class itself.

Note: read more about mock objects in PHPUnit .

The good news is that symfony2 allows it. Now all your controllers can act as services. The previous, generally accepted version is also supported and indispensable for small controllers that do not require unit-testing.

In order for the controller from the example above to interact correctly with Symfony2 and work as it should, we need the following.

Create a controller class:
<?php

namespace Company\ApplicationBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Templating\Engine;

class IndexController
{
/**
* @var Symfony\Bundle\FrameworkBundle\Templating\Engine
*/
private $templating;

/**
* @param Symfony\Bundle\FrameworkBundle\Templating\Engine $templating
*/
public function setTemplating(Engine $templating)
{
$ this ->templating = $templating;
}

/**
* @return Symfony\Component\HttpFoundation\Response
*/
public function indexAction()
{
return $ this ->templating->render( 'ApplicationBundle:Index:index' );
}
}

Create a DIC configuration using the following xml:
<? xml version ="1.0" ? >

< container xmlns ="http://www.symfony-project.org/schema/dic/services"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://www.symfony-project.org/schema/dic/services www.symfony-project.org/schema/dic/services/services-1.0.xsd" >

< services >
< service id ="index_controller" class ="Company\ApplicationBundle\Controller\IndexController" >
< call method ="setTemplating" />
< argument type ="service" id ="templating" />
</ call >
</ service >
</ services >
</ container >

Create a routing configuration:
<? xml version ="1.0" encoding ="UTF-8" ? >

< routes xmlns ="http://www.symfony-project.org/schema/routing"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://www.symfony-project.org/schema/routing www.symfony-project.org/schema/routing/routing-1.0.xsd" >

< route id ="index" pattern ="/index" >
< default key ="_controller" > index_controller:indexAction </ default >
</ route >
</ routes >

Note: in the example above, service_id: action was used instead of the usual BundleBundle: Controller: action (without the 'Action' suffix).

When all this is done, we need to inform Symfony2 about our services. To prevent the creation of the Dependency Injection extension and the creation of a configuration file point, we can register our services directly:

<?php

namespace Company\ApplicationBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

class ApplicationBundle extends Bundle {
public function registerExtensions(ContainerBuilder $container) {
parent::registerExtensions($container);

// register controllers
$loader = new XmlFileLoader($container);
$loader->load(__DIR__. '/Resources/config/controllers.xml' );
}
}

Note: The described technique was originally announced by Kris Wallsmith during the joint development of the project in OpenSky.

Now everything is ready. You need to include the bundle level routing file in the application level routing configuration, create the Index directory. The final directory structure should resemble this:

Company
| - ApplicationBundle
| | - Controller
| | | - IndexController.php
| | - Resources
| | | - config
| | | | - controller_routing.xml
| | | | - controllers.xml
| | | - views
| | | | - Index
| | | | | - index.php
| | - ApplicationBundle.php

After completing these steps, you can try this in the browser by typing the URL:
your_application/your_front_controller.php/index

Source: https://habr.com/ru/post/105361/


All Articles