Testing Symfony2 Commands – Mocking the DI Container with Mockery

Some months ago I had the pleasure to contribute to phpcassa library, maintained by Tyler Hobbs, adding support for Composer and Travis, and fixing some issues with PHP5.4 and some E_STRICT warnings.

And, as there is no Symfony2 bundle for Cassandra yet, I have decided to create one myself. The bundle is at early stages but I have already developed some basic Administration commands, mainly to create a Keyspace, a ColumnFamily, etc… and I have decided to share here how I have unit-tested those commands.

Let’s be honest, PHPUnit is the de-facto standard for Unit Testing, we owe a lot to this amazing library but its mocking framework is a bit cumbersome and limited. And because of that, I’ve been using Mockery for some time and I am pretty happy about it.

So, let’s see how can unit test a Symfony2 Command with the help of Mockery! Let’s forget about pure TDD, we will start showing the Create Keyspace Command and afterwards we will see how to test it.

<?php
 
namespace ADR\Bundle\CassandraBundle\Command;
 
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use phpcassa\SystemManager;
 
class CassandraCreateKeyspaceCommand extends ContainerAwareCommand
{
    /**
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    private $input;
 
    /**
     *
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    private $output;
 
    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        parent::configure();
        $this
            ->setName('cassandra:keyspace:create')
            ->setDescription('Creates the configured keyspace in selected cluster')
            ->addArgument('client', InputArgument::REQUIRED, 'The client name in Symfony2 where keyspace will be created')
            ->addArgument('keyspace', InputArgument::REQUIRED, 'The keyspace name')
            ->setHelp(<<<EOT
The <info>cassandra:keyspace:create</info> command creates the configured keyspace in the selected cluster.
 
<info>app/console cassandra:keyspace:create [client] [keyspace]</info>
EOT
            );
    }
 
    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->output = $output;
 
        $keyspace = $input->getArgument('keyspace');
 
        $manager = $this->getContainer()->get('cassandra.' . $input->getArgument('client') . '.manager');
        $manager->create_keyspace($keyspace, array());
 
        $output->writeln('<info>Keyspace ' . $keyspace . ' successfully created at ' . $manager->getServer() . '</info>');
    }
}

In this bundle, via its Dependency Injection Extension file, a ‘cassandra.xxx.manager’ service is created (being xxx how we are naming that cassandra cluster) and this service is basically a wrapper for phpcassa\SystemManager class. Details on how to do it can be found here

But how can we unit test this command? Lots of widely used bundles do not test commands and yes, to be honest, testing this does not add big value but it can still work for a small Container mocking example.

So, to unit test the command, we could do something like this:

<?php
 
namespace ADR\Bundle\CassandraBundle\Tests\Command;
 
use Symfony\Component\Console\Application;
use ADR\Bundle\CassandraBundle\Command\CassandraCreateKeyspaceCommand;
use Symfony\Component\Console\Tester\CommandTester;
use Mockery as m;
 
class CassandraCreateKeyspaceCommandTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider getNonInteractiveData
     */
    public function testCreateKeyspaceCommand($input)
    {
        $application = new Application();
        $application->add(new CassandraCreateKeyspaceCommand());
 
        $command = $application->find('cassandra:keyspace:create');
        $command->setContainer($this->getMockContainer($input));
 
        $tester = new CommandTester($command);
        $tester->execute(
            array_merge(array('command' => $command->getName()), $input)
        );
 
        $this->assertEquals('Keyspace ' . $input['keyspace'] . ' successfully created at #mockServer#' . PHP_EOL, $tester->getDisplay());
    }
 
    private function getMockContainer($input)
    {
        $systemManager = m::mock('phpcassa\SystemManager');
        $systemManager
            ->shouldReceive('create_keyspace')
            ->once()
            ->with($input['keyspace'], array())
        ;
 
        $systemManager
            ->shouldReceive('getServer')
            ->once()
            ->withNoArgs()
            ->andReturn('#mockServer#')
        ;
 
        $container = m::mock('Symfony\Component\DependencyInjection\Container');
        $container
            ->shouldReceive('get')
            ->once()
            ->with('cassandra.' . $input['client'] . '.manager')
            ->andReturn($systemManager)
        ;
 
        return $container;
    }
 
    public function getNonInteractiveData()
    {
        return array(
            array(array('keyspace' => 'fooKeySpace', 'client' => 'barClient')),
        );
    }
}

The most interesting part is that we create a mocked phpcassa\SystemManager and a mocked Symfony\Component\DependencyInjection\Container with the calls and responses we expect from them.

As you can see, Mockery’s fluent interface seems to be more clear and intuitive than PHPUnit and makes it easier to test Symfony2 commands!

I hope you’ve liked this example and I promise to blog more often than these latest months!

You may also like...

5 Responses

  1. Paul Seiffert says:

    Thank you for sharing this post.
    I spent a couple of hours with console commands and different ideas of ways to test them. The biggest issue is that console commands do not really support dependency injection. I tried to change this, which turned out to be difficult. There are multiple design issues to solve.
    After trying to solve some via pull requests ( https://github.com/symfony/symfony/pull/6389 , https://github.com/symfony/symfony-docs/pull/2032 ), I decided to create a bundle that enables developers to define their commands as services. This makes dependency injection and especially unit testing really easy. Take a look at it and tell me what you think. https://github.com/seiffert/console-extra-bundle

  2. cordoval says:

    something is wrong here maybe or i am wrong

    please tell me

    $manager = $this->getContainer()->get(‘cassandra.’ . $input->getArgument(‘client’) . ‘.manager’);
    $manager->create_keyspace($keyspace, array());

    wouldn’t it be better to test the service/manager rather than the command?

    the symfony2 docu has some testing mechanism

    yeah if this blog was done to show mockery powers then fine, but it is a bit confusing to me maybe.

    Thanks great post nonetheless.

  3. Ricard Clau says:

    In this case, the manager is directly a phpcassa class which has its own tests in that library. The service that gets defined in the Extension class is basically a wrapper on it so I think that testing the service is worthless.

    And of course, one of the intentions was to show a bit of Mockery 🙂

  4. I’m curious to find out what blog system you have been utilizing? I’m having some minor security issues with my latest blog and I’d like to find something more risk-free. Do you have any recommendations?

  5. My coder is trying to persuade me to move to .
    net from PHP. I have always disliked the idea because of
    the costs. But he’s tryiong none the less. I’ve been using WordPress on numerous
    websites for about a year and am anxious about switching to another platform.

    I have heard excellent things about blogengine.net. Is there a
    way I can transfer all my wordpress posts into it? Any help would be really appreciated!