4

As part of learning Symfony2, I'm trying to write a very simple console command that simply runs phpcs (PHP Code Sniffer).

Here's the execute function which is in a class extending ContainerAwareCommand:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $output->writeln('<info>Generating PHP Code Sniffer report...</info>');
    exec('phpcs ./src > ./app/logs/phpcs.log');

    if ($input->getOption('noprompt') == null) {
        $dialog = $this->getHelperSet()->get('dialog');
        if ($dialog->askConfirmation($output, '<question>Open report in TextMate? (y/n)?</question>', false)) {
            exec('mate ./app/logs/phpcs.log');
        }
    }

    $output->writeln('<info>...done</info>');
}

I am able to execute the console command by running

app/console mynamespace:ci:phpcs

and it works perfectly. The output file is generated as expected.

I'm trying to test the mynamespace:ci:phpcs command using the following function (which is part of a PHPUnit_Framework_TestCase):

public function testExecute()
{
    $kernel = new \AppKernel("test", true);
    $kernel->boot();

    $application = new Application($kernel);
    $application->add(new PhpCodeSnifferCommand());

    $command = $application->find('mynamespace:ci:phpcs');
    $commandTester = new CommandTester($command);
    $commandTester->execute(array('command' => $command->getName()));

    // ... Test if output file was created here ... ommitted for brevity ... //
}

However, when trying to execute it via the unit test, it fails with the following output:

sh: phpcs: command not found

Does anyone have an idea why this is happening?

PS: One thing I did observe was, that when I comment out the lines in the command that call 'exec' the test runs through (not passing, but not moaning that phpcs doesn't exist), so the problem is definitely with the exec commands.

Does the PHPUnit tests run as a different user, where phpcs is not available?

hakre
  • 193,403
  • 52
  • 435
  • 836
josef.van.niekerk
  • 11,941
  • 20
  • 97
  • 157
  • How are you running PHPUnit: as yourself from the console, inside Jenkins or some other CI tool, from an IDE? Jenkins runs as the `jenkins` user on my Ubuntu system, but my IDE runs as me as does the console. – David Harkness Jan 22 '12 at 20:16
  • I'm normally running them from IntelliJ, but tried running from command line as well. – josef.van.niekerk Jan 23 '12 at 07:18
  • Make sure that `phpcs` has been installed on everyone's `$PATH` rather than just your own. This is typically the case, so it's a bit strange. Mocking does solve it but may just delay the problem downstream. – David Harkness Jan 23 '12 at 10:48

1 Answers1

5

For unit tests you should consider mocking the calls to exec(). This will speed up your tests and avoid environmental issues such as this. For this case you can simply add methods that call exec() to your class that you can mock for the tests.

class PhpCodeSnifferCommand extends ...
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // ...
        runReport();
        // ...
                viewReport();
        // ...
    }

    protected function runReport() {
        exec('phpcs ./src > ./app/logs/phpcs.log');
    }

    protected function viewReport() {
        exec('mate ./app/logs/phpcs.log');
    }
}

Mocking makes it easier to validate the three possible paths:

  1. The command is told not to prompt the user to view the report.
  2. The command is told to prompt; the user says no.
  3. The command is told to prompt; the user says yes.

Place each path in its own test. You could put the common code into a test helper method to make this much shorter.

public function testRunsReportWithoutAskingToView()
{
    // ...

    $application = new Application($kernel);
    $phpcs = $this->getMock('PhpCodeSnifferCommand', array('runReport', 'viewReport'));
    $phpcs->expects($this->once())->method('runReport');
    $phpcs->expects($this->never())->method('viewReport');
    $application->add($phpcs);

    // Tell the command not to prompt to view the report ...
}

public function testRunsAndViewsReport()
{
    // ...

    $application = new Application($kernel);
    $phpcs = $this->getMock('PhpCodeSnifferCommand', array('runReport', 'viewReport'));
    $phpcs->expects($this->once())->method('runReport');
    $phpcs->expects($this->once())->method('viewReport');
    $application->add($phpcs);

    // Tell the command to prompt to view and the dialog to hit "Y" for you ...
}

public function testRunsReportButDoesntViewIt()
{
    // ...

    $application = new Application($kernel);
    $phpcs = $this->getMock('PhpCodeSnifferCommand', array('runReport', 'viewReport'));
    $phpcs->expects($this->once())->method('runReport');
    $phpcs->expects($this->never())->method('viewReport');
    $application->add($phpcs);

    // Tell the command to prompt to view and the dialog to hit "N" for you ...
}
David Harkness
  • 35,992
  • 10
  • 112
  • 134
  • Thanks for your answer, this is definitely the best way to approach it. I don't know why I didn't think about mocking the calls to exec. Excellent advice! – josef.van.niekerk Jan 23 '12 at 07:20