Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test Symfony controllers

I'm trying to get a Symfony controller in a test harness using Codeception. Every method starts as follows:

public function saveAction(Request $request, $id)
{
    // Entity management
    /** @var EntityManager $em */
    $em = $this->getDoctrine()->getManager();

    /* Actual code here
    ...
    */
}

public function submitAction(Request $request, $id)
{
    // Entity management
    /** @var EntityManager $em */
    $em = $this->getDoctrine()->getManager();

    /* 200+ lines of procedural code here
    ...
    */
}

I've tried:

$request = \Symfony\Component\HttpFoundation\Request::create(
    $uri, $method, $parameters, $cookies, $files, $server, $content);

$my_controller = new MyController();
$my_controller->submitAction($request, $id);

from my unit tests, but it seems there's a lot of other setup I don't know about that Symfony does in the background. Every time I find a missing object and initialise it, there's another one that fails at some point.

I've also tried stepping through the test from PhpStorm, but PhpUnit has some output that causes Symfony to die before it gets anywhere near the code I'm trying to test because it can't start the $_SESSION after any output has occurred. I don't think this happens from the command line, but I'm not really close enough to tell yet.

How can I simply and extensibly run this code in a Unit Test?


A bit of background:

I inherited this code. I know it's dirty and smells because it's doing model logic in the controller. I know that what I'm asking for is not a "pure" unit test because it touches virtually the whole application.

But I need to be able to run just this "small" (200+ lines) bit of code automatically. The code should run in no more than a couple of seconds. I don't know how long because I've never been able to run it independently.

Currently, the setup time to run this code through the website is huge, as well as being complex. The code does not generate a web page, it's basically an API call that generates a file. I need to be able to generate as many of these test files as I like in a short amount of time as I'm making coding changes.

The code is what it is. It's my job to be able to make changes to it and at the moment I can't even run it without a huge overhead each time. It would be irresponsible to make changes to it without knowing what it's doing.

like image 290
CJ Dennis Avatar asked Apr 10 '18 04:04

CJ Dennis


1 Answers

Part of your problem is, that when you write a Controller that extends from Symfony's base Controller or the new AbstractController it will load other dependencies from the container. These service you either have to instantiate in your tests and pass them to a container, that you then set in your controller like this:

$loader = new Twig_Loader_Filesystem('/path/to/project/app/Resources/views');
$twig = new Twig_Environment($loader, array(
    'cache' => '/path/to/app/var/cache/templates',
));

# ... other services like routing, doctrine and token_storage

$container = new Container();
$container->set('twig', $twig);

$controller = new MyController();
$controller->setContainer($container);

or mock them, which makes your test pretty much unreadable and break on every change you do to the code.

As you can see, this is not really a unit test, because you will need all the services you pull from your container directly by calling $this->get()/$this->container->get() or indirectly, such via the helper methods in the controller, e.g. getDoctrine().

Not only is this tedious if you don't configure the services in the same way as you use in production, your tests might not be very meaningful, as they could pass in your tests but fail in production.

The other part of your problem is the comment inside your snippet:

200+ lines of procedural code here

Without seeing the code I can tell you that properly unit testing this is near impossible and not worth it.

The short answer is, you can't.

What I recommend is either writing Functional Tests using WebTestCase or something like Selenium with CodeCeption and test your controller indirectly through the UI.

Once you have tests covering the (main) functionality of your action, you can start refactoring your controller splitting things into smaller chunks and services that are easier to test. For those new classes unit tests make sense. You will know when the website works again as before your changes, when your Functional Tests are green (again). Ideally you would not need to change these first tests as they only look at your website through the browser and therefore are not affected by any code changes you do. Just be careful to not introduce changes to the templates and the routing.

like image 127
dbrumann Avatar answered Oct 22 '22 14:10

dbrumann