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.

 1<?php
 2
 3namespace ADR\Bundle\CassandraBundle\Command;
 4
 5use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
 6use Symfony\Component\Console\Input\InputInterface;
 7use Symfony\Component\Console\Output\OutputInterface;
 8use Symfony\Component\Console\Input\InputArgument;
 9use phpcassa\SystemManager;
10
11class CassandraCreateKeyspaceCommand extends ContainerAwareCommand
12{
13    /**
14     * @var \Symfony\Component\Console\Input\InputInterface
15     */
16    private $input;
17
18    /**
19     *
20     * @var \Symfony\Component\Console\Output\OutputInterface
21     */
22    private $output;
23
24    /**
25     * {@inheritDoc}
26     */
27    protected function configure()
28    {
29        parent::configure();
30        $this
31            ->setName('cassandra:keyspace:create')
32            ->setDescription('Creates the configured keyspace in selected cluster')
33            ->addArgument('client', InputArgument::REQUIRED, 'The client name in Symfony2 where keyspace will be created')
34            ->addArgument('keyspace', InputArgument::REQUIRED, 'The keyspace name')
35            ->setHelp(&lt;&lt;&lt;EOT
36The &lt;info>cassandra:keyspace:create&lt;/info> command creates the configured keyspace in the selected cluster.
37
38&lt;info>app/console cassandra:keyspace:create [client] [keyspace]&lt;/info>
39EOT
40            );
41    }
42
43    /**
44     * {@inheritDoc}
45     */
46    protected function execute(InputInterface $input, OutputInterface $output)
47    {
48        $this->input = $input;
49        $this->output = $output;
50
51        $keyspace = $input->getArgument('keyspace');
52
53        $manager = $this->getContainer()->get('cassandra.' . $input->getArgument('client') . '.manager');
54        $manager->create_keyspace($keyspace, array());
55
56        $output->writeln('&lt;info>Keyspace ' . $keyspace . ' successfully created at ' . $manager->getServer() . '&lt;/info>');
57    }
58}

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:

 1<?php
 2
 3namespace ADR\Bundle\CassandraBundle\Tests\Command;
 4
 5use Symfony\Component\Console\Application;
 6use ADR\Bundle\CassandraBundle\Command\CassandraCreateKeyspaceCommand;
 7use Symfony\Component\Console\Tester\CommandTester;
 8use Mockery as m;
 9
10class CassandraCreateKeyspaceCommandTest extends \PHPUnit_Framework_TestCase
11{
12    /**
13     * @dataProvider getNonInteractiveData
14     */
15    public function testCreateKeyspaceCommand($input)
16    {
17        $application = new Application();
18        $application->add(new CassandraCreateKeyspaceCommand());
19
20        $command = $application->find('cassandra:keyspace:create');
21        $command->setContainer($this->getMockContainer($input));
22
23        $tester = new CommandTester($command);
24        $tester->execute(
25            array_merge(array('command' => $command->getName()), $input)
26        );
27
28        $this->assertEquals('Keyspace ' . $input['keyspace'] . ' successfully created at #mockServer#' . PHP_EOL, $tester->getDisplay());
29    }
30
31    private function getMockContainer($input)
32    {
33        $systemManager = m::mock('phpcassa\SystemManager');
34        $systemManager
35            ->shouldReceive('create_keyspace')
36            ->once()
37            ->with($input['keyspace'], array())
38        ;
39
40        $systemManager
41            ->shouldReceive('getServer')
42            ->once()
43            ->withNoArgs()
44            ->andReturn('#mockServer#')
45        ;
46
47        $container = m::mock('Symfony\Component\DependencyInjection\Container');
48        $container
49            ->shouldReceive('get')
50            ->once()
51            ->with('cassandra.' . $input['client'] . '.manager')
52            ->andReturn($systemManager)
53        ;
54
55        return $container;
56    }
57
58    public function getNonInteractiveData()
59    {
60        return array(
61            array(array('keyspace' => 'fooKeySpace', 'client' => 'barClient')),
62        );
63    }
64}

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!