Getting clone method called on non-object when specing Symfony command that uses SymfonyStyle for styled output

2.3k views Asked by At

I'm trying to spec Symfony command and I want to have formated output with SymfonyStyle

<?php

namespace Acme\AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class SomeCommand extends ContainerAwareCommand
{
    //....
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);

        $io->title('Feed import initiated');
    }
}

and spec file:

<?php

namespace spec\Acme\AppBundle\Command;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class SomeCommandSpec extends ObjectBehavior
{
    //...
    function it_fetches_social_feeds(
        ContainerInterface $container,
        InputInterface $input,
        OutputInterface $output,
        SymfonyStyle $symfonyStyle
    ) {
        // With options
        $input->bind(Argument::any())->shouldBeCalled();
        $input->hasArgument('command')->shouldBeCalled();
        $input->isInteractive()->shouldBeCalled();
        $input->validate()->shouldBeCalled();

        $symfonyStyle->title(Argument::any())->shouldBeCalled();

        $this->setContainer($container);
        $this->run($input, $output);
    }
}

but I'm getting this error:

exception [err:Error("__clone method called on non-object")] has been thrown.
 0 vendor/symfony/symfony/src/Symfony/Component/Console/Style/SymfonyStyle.php:50
   throw new PhpSpec\Exception\ErrorException("__clone method called on ...")
 1 vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:866
   Symfony\Component\Console\Command\Command->run([obj:Symfony\Component\Console\Input\ArgvInput], [obj:Symfony\Component\Console\Output\ConsoleOutput])
 2 vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:193
   Symfony\Component\Console\Application->doRunCommand([obj:PhpSpec\Console\Command\RunCommand], [obj:Symfony\Component\Console\Input\ArgvInput], [obj:Symfony\Component\Console\Output\ConsoleOutput])
 3 vendor/phpspec/phpspec/src/PhpSpec/Console/Application.php:102
   Symfony\Component\Console\Application->doRun([obj:Symfony\Component\Console\Input\ArgvInput], [obj:Symfony\Component\Console\Output\ConsoleOutput])
 4 vendor/phpspec/phpspec/bin/phpspec:26
   Symfony\Component\Console\Application->run()
 5 vendor/phpspec/phpspec/bin/phpspec:28
   {closure}("3.2.2")

on line 50 of SymfonyStyle is:

public function __construct(InputInterface $input, OutputInterface $output)
{
    $this->input = $input;
    /* line 50 */ $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
    // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
    $this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);

    parent::__construct($output);
}

and phpspec is complainig about

clone $output->getFormatter()

Am I doing something wrong, or am I missing something?

Update

this is my let method:

function let(SymfonyStyle $symfonyStyle, InputInterface $input, OutputInterface $output)
{
    $symfonyStyle->beConstructedWith([$input->getWrappedObject(), $output->getWrappedObject()]);
}
2

There are 2 answers

0
DonCallisto On BEST ANSWER

You are missing this

function it_fetches_social_feeds(
    ContainerInterface $container,
    InputInterface $input,
    OutputInterface $output,
    SymfonyStyle $symfonyStyle
) {
    // .... other code  
    $prophet = new Prophet();
    $formatter = $prophet->prophesize(OutputFormatterInterface::class);
    $output->getFormatter()->willReturn($formatter);
    // .... more code
}

You can place it where you want but before the call is done.

In that way you have created a Stub that is basically a Double with behavior and without expectations. You can see it as a "proxy" that will intercept method calls and returns what you "teach" it to return.
In your example things get broken as your double OutputInterface would return null as it's not a "real" object.
I also suggest to stub the getVerbosity behavior if you need to do different kind of specs.

BTW you can read more about doubles in phpspec guide and prophecy guide

1
Rooneyl On

Looking at an example, SymfonyStyle never seems to be passed in the spec class.
The execute function of the command doesn't pass it either, just the input and output classes (the style class it created as a local variable).