Symfony unit testing & the container - symfony

When unit testing in Symfony 2, the controller I'm testing does not receive the service container causing the test to fail with the good old Call to a member function get() on a non-object
I can't use $this->forward from the testing controller as it also does not have the service container.
I found this reference but it seems as though I would be using it for the wrong reason, has anyone had any experience with this?
http://symfony.com/doc/current/book/testing.html#accessing-the-container
Edit: Here is my test:
<?php
namespace HvH\ClientsBundle\Tests\Controller;
use HvH\ClientsBundle\Controller\ClientsController;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\HeaderBag;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ClientsControllerTest extends WebTestCase
{
public function testGetClientsAction()
{
$client = static::createClient();
$container = $client->getContainer();
$session = $container->get('session');
$session->set('key', 'value');
$session->save();
$request = new Request;
$request->create('/clients/123456', 'GET', array(), array(), array(), array(), '');
$headers['X-Requested-With'] = "XMLHttpRequest";
$request->headers->add($headers);
/* This doesn't work */
/*
$controller = new Controller;
$status = $controller->forward( 'HvHClientsBundle:Clients:getClients', array('request' => $request) );
*/
$clients_controller = new ClientsController();
$status = $clients_controller->getClientsAction($request);
$this->assertEquals(200, $status);
}
}
Here is the part of the clients controller where it fails
<?php
namespace HvH\ClientsBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use HvH\APIschemaBundle\Controller\Validation;
//FOSRestBundle
use FOS\RestBundle\View\View;
class ClientsController extends Controller
{
//Query all clients
public function getClientsAction(Request $request)
{
$request_type = $request->headers->get('X-Requested-With');
if($request_type != 'XMLHttpRequest') {
return $this->render('HvHDashboardBundle:Dashboard:dashboard.html.twig' );
}
//get any query strings
$query_strings = $request->query->all();
$definition = $this->get("service.handler")->definition_handler(__CLASS__, __FUNCTION__);
//once data has been prepared
return $this->get('fos_rest.view_handler')->handle($view);
}
}

I think the reason the controller isn't getting a container is because you are trying to instantiate and interact with it directly rather than simulating requests using the client (See the section on Functional Tests in the Testing section of the Symfony2 book).
You need something more like this (not sure if the route is correct):
public function testGetClientsAction()
{
$client = static::createClient();
$client->request(
'GET', '/clients/123456',
array(), /* request params */
array(), /* files */
array('X-Requested-With' => "XMLHttpRequest"),
);
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
Note also, the request() method returns a crawler instance which provides helper methods to verify the content of the response if required.

Related

Repository usage in configureActionButtons (Sonata Admin Bundle)

I am trying to inject doctrine service (if there is a way to inject BoothTypeRepository without Doctrine, that's also fine) into configureActionButtons and I can't find the way to inject nor doctrine or BoothTypeRepository.
public function configureActionButtons($action, $object = null): array
{
$list = parent::configureActionButtons($action, $object);;
$handles = $this->getConfigurationPool()->getContainer()->get('doctrine.orm.entity_manager')->getRepository(BoothTypeRepository::class)->findAll();
foreach($handles as $handle) {
$list['new_' . $handle] = [
'attr' => $handle,
'template' => 'CRUD/button_new_booth_type.html.twig'
];
}
unset($list['new']);
return $list;
}
The Error which I got from the upper code
An exception has been thrown during the rendering of a template
("Class "App\Repository\BoothTypeRepository" sub class of
"Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository" is
not a valid entity or mapped super class.").
This is the namespace for BoothTypeRepository -> namespace App\Repository;
Answer to this is in construct:
private BoothTypeRepository $boothTypeRepository;
public function __construct($code, $class, $baseControllerName, BoothTypeRepository $boothTypeRepository)
{
parent::__construct($code, $class, $baseControllerName);
$this->boothTypeRepository = $boothTypeRepository;
}
Then one don't need to use doctrine service ->
$boothTypes = $this->boothTypeRepository->findAll();

How to submit form in PHPunit with symfony?

I am using symfony 3.0 and phpunit 3.7.1.I want to submit a form via phpunit file.
File Name: abcControllerTest.php
<?php
namespace AbcBundle\Tests\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request;
class AbcControllerTest extends WebTestCase {
public function testEmpty() {
$whereParams = array('params' => array(
'var' => '',
));
return $whereParams;
}
/**
* ABC Builder
* #depends testEmpty
*/
public function testAbc(array $whereParams) {
$client = static::createClient();
$crawler = $client->request('GET', $GLOBALS['host'] . '/abc/ac_file/param', $whereParams);
$buttonCrawlerNode = $crawler->selectButton('Submit');
$form = $buttonCrawlerNode->form();
$crawler = $client->submit($form);
}
}
I am testing export report functionality thats why i am just creating a submit button and trying to submit form on specific url with some parameter.
Error Message: InvalidArgumentException: The current node list is empty.
Anyone can suggest me what can i do?

Add test controller and route to test event listener

How can I test event listener in the Symfony bundle?
I plan to test it with client (send request and get response). But I have no controllers in my bundle. Can I add 'special' controllers and routes from functional test and test output from them?
I've found way how to add controllers for test.
First - create new controller class (I created it in %BundleName%/Tests/Controller)
%BundleName%/Tests/Controller/TestController.php
namespace %BundleName%\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class TestController extends Controller
{
public function rootAction()
{
return new Response('This is home page');
}
public function galleryAction($id)
{
return new Response(sprintf('This is gallery %s', $id));
}
}
Then I used this controller in test.
%BundleName%/Tests/Controller/PageControllerTest.php
namespace %BundleName%\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Routing\Route;
class PageControllerTest extends WebTestCase
{
/**
* #var \Symfony\Bundle\FrameworkBundle\Client
*/
private $client;
public function setUp()
{
$this->client = static::createClient();
$this->client->followRedirects(true);
$this->setUpRoutes();
}
public function testFirst()
{
$crawler = $this->client->request('GET', '/gallery/42');
}
protected function setUpRoutes()
{
$container = $this->client->getContainer();
/** #var \Symfony\Bundle\FrameworkBundle\Routing\Router $router */
$router = $container->get('router');
$collection = $router->getRouteCollection();
foreach ($collection->all() as $routeId => $route) {
//Leave some routes if you need...
$collection->remove($routeId);
}
$controllerClassName = '\%BundleName%\Tests\Controller\TestController';
$rootRoute = new Route('/', array('_controller' => sprintf('%s::%s', $controllerClassName, 'rootAction')));
$galleryRoute = new Route('/gallery/{id}', array('_controller' => sprintf('%s::%s', $controllerClassName, 'galleryAction')));
$collection->add('_test_root_route', $rootRoute);
$collection->add('_test_gallery_route', $galleryRoute);
}
}
On each test start setUpRoutes method clears route list and registers new routes. Each route _controller param value is \%BundleName%\Tests\Controller\TestController::nameOfAction'.

How to set a header and render a twig template without renderView() method in symfony2.X controller

How would one go about setting a header (Content Type) and rendering a twig template without renderView() method in symfony2.X controller?
I'm not sure if the accepted answer is valid anymore, or if ever was. Anyhow, here's a couple ways of doing it. I've got an XML, and JSON sample for you here.
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class DefaultController extends Controller{
public function indexAction($x = 0)
{
$response = new Response(
$this->renderView('AcmeMyBundle:Default:index.html.twig', array('x'=>$x)),
200
);
$response->headers->set('Content-Type', 'text/xml');
return $response;
}
//...
or for JSON response
//...
public function indexAction( $x = 0 )
{
$response = new JsonResponse(
array('x'=>$x)
);
return $response;
}
You can do it returning the response as rendered view (check this sample)
public function indexAction()
{
// a parameter which needs to be set in twig
$variable = 'This is sample assignment';
$current_user = $this->user; // assume you defined a private variable in your class which contains the current user object
$response = new Response(
'AcmeMyBundle:Default:myTemplate.html.twig',
['parameter1' => $variable],
['user' => $current_user]
);
return $response;
}
If your response has a specific header info you can easily set by $response->header->set(...);

Symfony2 Custom Error Exception Listener - Rendering templates or passing to a controller

I'm trying to work out the best way of handling custom error pages within Symfony2.This includes 500 and 404's etc.
I can create my own custom templates (error404.html.twig etc) and render these out fine, the issue is , the app requires a few variables be passed into the base template for the page to remain consistent. Using the built in exception handler results in required variables not being available.
I have successfully setup a custom Exception Event Listener, and registered it as a service:
namespace MyCo\MyBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class MyErrorExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// We get the exception object from the received event
$exception = $event->getException();
if($exception->getStatusCode() == 404)
{
//$engine = $this->container->get('templating');
//$content = $engine->render('MyBundle:Default:error404.html.twig');
//return $response = new Response($content);
/* Also Tried */
//$templating = $this->container->get('templating');
//return $this->render('MyBundle:Default:index.html.twig');
$response = new Response($templating->render('MyBundle:Exception:error404.html.twig', array(
'exception' => $exception
)));
$event->setResponse($response);
}
}
}
This doesn't work , as :$container is not available , meaning I cannot render my custom page.
So two questions really , is this the correct way to handle custom error pages, or should I pass the response off to a controller? If so , whats the best way of doing that?
If this is correct , how can I make the templating engine available within my Listener ?
You should add into yours Listener
/**
*
* #var ContainerInterface
*/
private $container;
function __construct($container) {
$this->container = $container;
}
How do you register your Listener?
You should register Listener like Service
Like that
core.exceptlistener:
class: %core.exceptlistener.class%
arguments: [#service_container]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: 200 }
The best way is don't use service_container.
The best way is register only necessary services.
Like that
/**
*
* #var Twig_Environment
*/
private $twig;
function __construct($twig) {
$this->twig = $twig;
}
The way I did to solve similar issue is: I dont know if it can help you out.
const GENERIC_CODE = 550;
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof RedirectableException) {
$request = $event->getRequest();
$url = $exception->getUrl($this->_authentication['base']['url']);
$response = new Response();
$response->setStatusCode(self::GENERIC_CODE, AuthenticationException::GENERIC_MESSAGE);
$response->setContent($url);
$event->setResponse($response);
}
}

Resources