Test your commands in Symfony2

DEPRECATED

This article is deprecated. Please refer to the Symfony documentation: http://symfony.com/doc/current/components/console/introduction.html#testing-commands

Console application is very useful when dealing with a web application. For example, in a personal project I'm currently working on, I created a bunch of commands to setup some parts:

$ php app/console myproject:user-create "Alexandre Salomé" alex 'alex@example.org'
User created
$ php app/console myproject:user-role-add alex admin
User role created

The problem is that we don't really know how to test them. If you inject dependencies into it, you can test dependencies and rely on a thin-enough command class.

But if you have more logic in it, you should better test it.

Unit or functional?

To unit-test it, you have to know which dependencies are required. In Symfony2, to execute a Command, you need to have a kernel and a container in it. If your command relies on services, each service need to be mocked, too.

In my opinion, it's too much services to be unit tested. So let's go ahead for a functional test.

Functional tests will launch the application in the "test" environment, like WebTestCase. Also, we will extend this class for the CLI tests.

Code, please

<?php

namespace Acme\Bundle\DemoBundle\Test;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Bundle\FrameworkBundle\Client;

/**
 * Base class for testing the CLI tools.
 *
 * @author Alexandre Salomé <alexandre.salome@gmail.com>
 */
abstract class CommandTestCase extends WebTestCase
{
    /**
     * Runs a command and returns it output
     */
    public function runCommand(Client $client, $command)
    {
        $application = new Application($client->getKernel());
        $application->setAutoExit(false);

        $fp = tmpfile();
        $input = new StringInput($command);
        $output = new StreamOutput($fp);

        $application->run($input, $output);

        fseek($fp, 0);
        $output = '';
        while (!feof($fp)) {
            $output = fread($fp, 4096);
        }
        fclose($fp);

        return $output;
    }
}

Sample, please

<?php

 use MyProject\MyProjectBundle\Test\CommandTestCase;

 class MyTest extends CommandTestCase
 {
     public function testDefaultDoesNotInstall()
     {
         $client = self::createClient();
         $output = $this->runCommand($client, "myproject:user-create alex 'Alexandre Salomé' 'alex@example.org'");

         $em = $client->getKernel()->getContainer()->get('doctrine')->getEntityManager();

         $user = $em->getRepository('MyProjectBundle:User')->findOneBy(array(
             'username' => 'alex'
         ));

         $this->assertContains('User created', $output);
         $this->assertEquals('Alexandre Salomé', $user->getFullname());
         $this->assertEquals('alex@example.org', $user->getEmail());
         $this->assertEquals(true, $user->getIsActive());
     }
 }

Here we are, we can now test our CLI edge cases and make the CLI command tools part of the test coverage.

Christian Jennewein December 30, 2011

Well done, merci !

cordoval January 1, 2012

you rock man, now i am trying tdd with this

bux April 16, 2012

Super ! Je vais m'en servir tout de suite d'ailleurs ;)

predakanga August 30, 2012

Very nice - you should make a PR to include this in FrameworkBundle (though I might use a memory writer instead of a temp file), it's very handy