Test your commands in Symfony2
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