9
votes

As we start using Symfony 2 on several projects now, we find that there is quite a bit of code we could share among our projects. So we started extracting features into Symfony 2 bundles in order to share them between our projects.

While we basically got things working, there are quite a few questions remaining that aren't easily googleable, especially when it comes to testing the shared bundle.

The first small bundle we extracted contains a Doctrine Entity, a kernel.event_listener that's being auto-injected into the client project's DI container, an annotation, another service and a couple of commands. The basic idea is that the client project can annotate its controllers with our annotation, the event_listener will intercept requests to the annotated controllers and execute some additional logic (involving the doctrine entity) before the controller is eventually invoked. The commands are intended to administer database entries of the doctrine entity.

So far, everything works exactly as we expected, but we're struggling with the testability of the bundle alone. First off, the Git repository that holds the bundle doesn't contain a complete Symfony2 project. That would be overkill as we're only building a bundle here, not a whole application, right?

But how can we test the event listener? How can we test it's being injected into the DI container? We'd need a test Controller that's gonna be annotated with our special annotation, so we can test that our event listener captures it correctly. That controller must only ever be available when testing and must never show up in any client application.

How can we test the Commands? We'd need to mock the database behind doctrine. When we try to execute the command in a phpunit test that's simply bootstrapped with /vendor/autoload.php, of course we get:

Fatal error: Call to undefined method Symfony\Component\Console\Application::getKernel() in /.../vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.php on line 3

So it feels like we're gonna end up needing a whole Symfony2 project in our bundle's repository anyway in order to bootstrap the whole framework to be able to eventually test our components. When I looked at open source Symfony2 bundles, I found none that had the whole Framework checked into their Git repos, so that still feels wrong.

What am I missing? Is there a piece of documentation about Bundle-Only / Applicationless bundle development I am missing?

Edit:

I found a solution for command testing here: http://www.ricardclau.com/2013/02/testing-symfony2-commands-mocking-the-di-container-with-mockery/

It turned out the error came from ContainerAwareCommand trying to create a new container, which obviously wouldn't work in a bare test environment. I solved the problem by mocking the container and injecting it into the Command manually like so:

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

class MyCommandTest extends \PHPUnit_Framework_TestCase {

    public function testExecute() {
        $application = new Application();
        $application->add(new MyCommand());

        $command = $application->find('my:command');
        $command->setContainer($this->getMockContainer()); // <= This avoids ContainerAwareCommand creating a 'real' container in a test env
        $commandTester = new CommandTester($command);
        $commandTester->execute(array('command' => $command->getName()));

        print $commandTester->getDisplay();

        $this->assertRegExp('/.../', $commandTester->getDisplay());
    }

    protected function getMockContainer() {
        // Mock the container and everything you'll need here
        $mockDoctrine = $this->getMock('Symfony\Bridge\Doctrine\RegistryInterface');
        $mockDoctrine->...;

        $mockContainer = $this->getMock('Symfony\Component\DependencyInjection\Container');
        $mockContainer->expects($this->once())
                      ->method('get')
                      ->with('doctrine')
                      ->willReturn($mockDoctrine);
        return $mockContainer;
    }
}

I guess the Controller testing will have to work in a similar, mock-heavy, way. When I find a solution for it, I'll post a complete answer here...

1
I'm not really an expert (not too much time to experiment and too much work) but I think that you need only a subset of dependencies like framework-bundle, console, http-kernel to setup and run your tests.gremo
Maybe your edit should be an answer... Anyway, good starting point. Thanks!Aerendir

1 Answers

0
votes

This question is quite old, but I just stumbled upon it.

Bundles are a Symfony specific way to share entire libraries incl. their respective dependency injection container configuration.

Therefore, bundles depend on the Symfony kernel and if one bundle depends on another bundle, you create heavy dependencies, which effectively prevents unit testing: To test a unit (a class) in bundle A, you'll need bundle B plus the Symfony kernel.

You can still test, but the resulting tests are acceptance / integration tests, not unit tests anymore. Those are not suitable for test driven development, slow and fragile, as you already noticed.

Update

I just wrote a blog post about this: https://lastzero.net/2015/11/dependent-symfony-2-bundles-and-testability/