test command symfony with phpunit - php

I create some basic command with symfony3.2 to generate some newsletter periodically
I'm dealing with some issue when i want to test my symfony command with phpunit 5.5.4.
It fail from the beginning:
/**
* #param InputInterface $input
* #param OutputInterface $output
*/
protected function execute(InputInterface $input, OutputInterface $output){
$output->writeln("<info>Script start</info>");
//...
$output->writeln("<info>done</info>");
}
with this unit test:
use MyBundle\Command\MyCommand;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
class MyCommandTest extends KernelTestCase
{
public function testExecute(){
$kernel = static::createKernel();
$kernel->boot();
$application = new Application($kernel);
$application->add(new MyCommand());
$command = $application->find('generate:newsletter');
$commandTester = new CommandTester($command);
$commandTester->execute(array(
'command' => $command->getName()
));
$output = $commandTester->getDisplay();
$this->assertContains('done',$output);
}
}
I follow this step by step but in my case i get :
Error: Call to a member function writeln() on string
MyBundle/Command/MyCommand.php:197
vendor/symfony/symfony/src/Symfony/Component/Console/Command/Command.php:262
vendor/symfony/symfony/src/Symfony/Component/Console/Tester/CommandTester.php:84
MyBundle/Command/MyCommandTest.php:34
It seems like commandTester don't put correct parameter in execute method from MyCommand.
I'm wondering if it's not CommandTesterClass issue.
That's why i'm here, to share with you that and find some solution together.
Thank you in advance

Method 'getDisplay()' returns a string as you can see from the Api doc:
http://api.symfony.com/3.0/Symfony/Component/Console/Tester/CommandTester.html and you're assigning that string to your $output variable.
I think what you need is 'getOutput()'

Related

Symfony phpunit test is catching a service's exception, but works fine when command is manually run

I'm working to create a test for a very simple Symfony console command. I'm using:
Symfony 5.4 (vanilla install with no changes to services.yaml or really any other config files)
PHP 8.0
PHPUnit 9.5
I have a service called Model with a public method that looks like the following:
public function throwException(): void
{
throw new \Exception('testing');
}
My command uses Dependency Injection to add the Model service to $model. Then the Command has the following for the execute method:
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->model->throwException();
$styledIO = new SymfonyStyle($input, $output);
$styledIO->success("Done");
return 0;
}
When manually executing the command, I get the expected result (in red indicating a failure):
testing
This is my basic unit test for this command:
class TestCommandTest extends PHPUnit\Framework\TestCase
{
public function testException(): void
{
$application = new Symfony\Component\Console\Application();
$modelMock = $this
->getMockBuilder(App\Model::class)
->getMock();
$application->add(new App\TestCommand($modelMock));
$commandTester = new Symfony\Component\Console\Tester\CommandTester($application->find('app:test'));
$commandTester->execute([]);
var_dump($commandTester->getDisplay());
}
}
When running the unit test, it executes testException(), but continues as if $this->model->throwException(); didn't throw an exception. So the dump from testing that method returns:
[OK] Done
With a status code of 0. As if something is catching the exception from that service.
If I throw an exception directly in the command like so:
protected function execute(InputInterface $input, OutputInterface $output): int
{
throw new \Exception('testing 2');
$this->model->throwException();
$styledIO = new SymfonyStyle($input, $output);
$styledIO->success("Done");
return 0;
}
The unit test picks up that exception and properly indicates that the command is throwing an exception.
Any idea why the service's exception is getting caught and how to disable that?
Thanks a lot.

How to call method that takes interface as a parameter? Symfony - PHP

I feel so stupid, but I don't know how to call a function that takes interface as a parameter. So I added a method in my class:
public function sendSpool($messages = 10, KernelInterface $kernel)
{
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array(
'command' => 'swiftmailer:spool:send',));
$output = new BufferedOutput();
$application->run($input, $output);
$content = $output->fetch();
return new Response($content);
}
How would I call it from my controller?
I tried:
$this->sendSpool('test', KernelInterface::class);
Then:
$kernel = new HttpKernel();
$this->sendSpool('test', $kernel );
This Kernel interface is in my use statement
use Symfony\Component\HttpKernel\KernelInterface;
, but I don't know how to pass it, please help explaining it to me if someone has couple of minutes. Thanks.
You need to use an instance of a class that implements the interface. Basically, it's asking to give you a class that has the functions available as described in the interface.
class MyKernel implements KernelInterface { /* functions here */ }
class x {
function x() {
$obj = new MyKernel();
$this->sendSpool('test', $obj);
}
}
In Symfony usually there is a way to get your kernel. Something like:
$this->getApplication()->getKernel()
But it depends on your use case.
Also, you need the use statement in the class that implements the interface, you don't need it on the place where you are using it.

PHP symfony4: Dependency injection inside KernelTestCase for command

Hello I'm trying to create a unit-test for a symfony4 console command but I can't inject the dependencies correctly. I'm very new to symfony4 so maybe this is a basic question for you guys.
My unit test looks like this:
<?php
namespace App\Tests\Command;
use App\Command\ExecuteSalesTaskCommand;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
use Psr\Log\LoggerInterface;
use App\Repository\TaskRepository;
class ExeculteSalesTaskCommandTest extends KernelTestCase
{
/**
* #param LoggerInterface $logger
* #param TaskRepository $taskRepository
*/
public function testExecute(LoggerInterface $logger, TaskRepository $taskRepository)
{
$kernel = self::bootKernel();
$application = new Application($kernel);
$application->add(new ExecuteSalesTaskCommand($logger,$taskRepository));
# UPDATED
$logger = self::$kernel->getContainer()->get(LoggerInterface::class);
$taskRepository = self::$kernel->getContainer()->get(TaskRepository::class);
$command = $application->find('app:execute-sales-task');
$commandTester = new CommandTester($command);
$commandTester->execute(
[
'command' => $command->getName(),
]
);
// the output of the command in the console
$output = $commandTester->getDisplay();
$this->assertContains('Execute sales resulted: ', $output);
}
}
My problem is that I get injection errors like this:
ArgumentCountError: Too few arguments to function
App\Tests\Command\ExeculteSalesTaskCommandTest::testExecute(), 0
passed and exactly 2 expected
UPDATE:
When I fetch the dependencies out of the container I get this kind of error:
There was 1 error:
1) App\Tests\Command\ExeculteSalesTaskCommandTest::testExecute
Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException:
The "Psr\Log\LoggerInterface" service or alias has been removed or
inlined when the container was compiled. You should either make it
public, or stop using the container directly and use dependency
injection instead.
How can I inject the necessary dependencies correctly, so I can create an instance of the ExecuteSalesTaskCommand?
Well I found out that the issue was that I tried to load the dependencies manually. Use the autowiring instead like this:
public function testExecute()
{
$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/.env.test');
$kernel = self::bootKernel();
$application = new Application($kernel);
$executeSalesCommand = self::$kernel->getContainer()->get(
'console.command.public_alias.App\Command\ExecuteSalesTaskCommand'
);
$application->add($executeSalesCommand);
$command = $application->find('app:execute-sales-task');
$commandTester = new CommandTester($command);
$commandTester->execute(
[
'command' => $command->getName(),
]
);
// the output of the command in the console
$output = $commandTester->getDisplay();
// do your asserting stuff
}
You need to get the command itself from the kernel container. Now it works.
I was also encountered similar issue. But following worked for me. I don't think you need to add command to the application and again to find it? Please find following solution. It might help others.
<?php
// BaseCommandTester.php
/**
* This is basis for the writing tests, that will cover commands
*
*/
namespace App\Tests\Base;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Dotenv\Dotenv;
class BaseCommandTester extends KernelTestCase
{
/**
* #var
*/
private $application;
/**
* to set test environment and initiate application kernel
*/
public function setUp()
{
/**
* get test env
*/
$dotenv = new Dotenv();
$dotenv->load('/var/www/.env.test');
/**
* boot kernel
*/
$kernel = self::bootKernel();
$this->application = new Application($kernel);
parent::setUp();
}
/**
* #return mixed
*/
public function getApplication()
{
return $this->application;
}
/**
* #param mixed $application
*/
public function setApplication($application)
{
$this->application = $application;
}
}
Test Case
// FeedUpdaterCommandTest.php
<?php
namespace App\Tests\Command;
use App\Tests\Base\BaseCommandTester;
use Symfony\Component\Console\Tester\CommandTester;
class FeedUpdaterCommandTest extends BaseCommandTester
{
/**
* test to update all feeds
*/
public function testExecuteUpdateAll() {
/**
* init command tester and executre
*/
$commandName = 'app:feedUpdater';
$expectedResult = '[OK] Update Success Feed Type : All';
$command = $this->getApplication()->find($commandName);
$commandTester = new CommandTester($command);
$commandTester->execute(array(
'command' => $command->getName()
));
/**
* get result and compare output
*/
$result = trim($commandTester->getDisplay());
$this->assertEquals($result, $expectedResult);
}
}
Result of Test run
#Run tests
root#xxx:/var/www# bin/phpunit tests/Command
#!/usr/bin/env php
PHPUnit 6.5.13 by Sebastian Bergmann and contributors.
Testing tests/Command
2018-11-28T07:47:39+00:00 [alert] Successful update of popularProducts Feeds!
2018-11-28T07:47:39+00:00 [alert] Successful update of topVouchers Feeds!
. 1 / 1 (100%)
Time: 1.44 seconds, Memory: 12.00MB
OK (1 test, 1 assertion)
I am using following Sf4 version
-------------------- --------------------------------------
Symfony
-------------------- --------------------------------------
Version 4.1.7
Service Defination and its by default private
#config/services.yml
App\Service\FeedGenerator:
arguments:
$feeds: '%feed_generator%'
I don't think you need to autowire again.

Symfony console - displaying help for command with no arguments

I'm developing a pretty simple Symfony console application. It has just one command with one argument, and a few options.
I followed this guide to create an extension of the Application class.
This is the normal usage for the app, and it works fine:
php application <argument>
This also works fine (argument with options):
php application.php <argument> --some-option
If someone runs php application.php without any arguments or options, I want it to run as though the user had run php application.php --help.
I do have a working solution but it isn't optimal and is perhaps slightly brittle. In my extended Application class, I overrode the run() method as follows:
/**
* Override parent method so that --help options is used when app is called with no arguments or options
*
* #param InputInterface|null $input
* #param OutputInterface|null $output
* #return int
* #throws \Exception
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
if ($input === null) {
if (count($_SERVER["argv"]) <= 1) {
$args = array_merge($_SERVER["argv"], ["--help"]);
$input = new ArgvInput($args);
}
}
return parent::run($input, $output);
}
By default, Application::run() is called with a null InputInterface, so here I figured I could just check the raw value of the arguments and forcefully add a help option to pass to the parent method.
Is there a better way to achieve this?
I managed to work out a solution which didn't involve touching the Application class at all. To call the help command from within another command:
/**
* #param InputInterface $input
* #param OutputInterface $output
* #return int
* #throws \Symfony\Component\Console\Exception\ExceptionInterface
*/
protected function outputHelp(InputInterface $input, OutputInterface $output)
{
$help = new HelpCommand();
$help->setCommand($this);
return $help->run($input, $output);
}
To do a specific action depending on command, you can use an EventListener which is called when the onConsoleCommand is fired.
The listener class should work as follows :
<?php
namespace AppBundle\EventListener;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Command\HelpCommand;
class ConsoleEventListener
{
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$application = $event->getCommand()->getApplication();
$inputDefinition = $application->getDefinition();
if ($inputDefinition->getArgumentCount() < 2) {
$help = new HelpCommand();
$help->setCommand($event->getCommand());
return $help->run($event->getInput(), $event->getOutput());
}
}
}
The service declaration :
services:
# ...
app.console_event_listener:
class: AppBundle\EventListener\ConsoleEventListener
tags:
- { name: kernel.event_listener, event: console.command, method: onConsoleCommand }

Troubles with GO! Aspect-Oriented Framework

If someone work with GO! framework, can you help me.
I install framework on php 5.3.13. Demo example is working.
But my own example doesn't work. Aspect(method beforeMethodExecution) is not perfomed.
Here is my code.
Main file:
//1 Include kernel and all classes
if (file_exists(__DIR__ .'/../../vendor/autoload.php')) {
$loader = include __DIR__ .'/../../vendor/autoload.php';
}
// 2 Make own ascpect kernel
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;
class Kernel extends AspectKernel{
/**
* Configure an AspectContainer with advisors, aspects and pointcuts
*
* #param AspectContainer $container
*
* #return void
*/
public function configureAop(AspectContainer $container)
{
}
}
//3 Initiate aspect kernel
$Kernel = Kernel::getInstance();
$Kernel->init();
//4 Include aspect
include(__DIR__.'/aspectclass/AspectClass.php');
$aspect = new DebugAspect();
//5 register aspect
$Kernel->getContainer()->registerAspect($aspect);
//6 Include test class
include(__DIR__.'/class/class1.php');
//7 Execute test class
$Class = new General('test');
$Class->publicHello();
File with test class:
class General{
protected $message = '';
public function __construct($message)
{
$this->message = $message;
}
public function publicHello()
{
echo 'Hello, you have a public message: ', $this->message, "<br>", PHP_EOL;
}
}
File with aspect:
use Go\Aop\Aspect;
use Go\Aop\Intercept\FieldAccess;
use Go\Aop\Intercept\FunctionInvocation;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\After;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use Go\Lang\Annotation\Pointcut;
use Go\Lang\Annotation\DeclareParents;
use Go\Lang\Annotation\DeclareError;
class DebugAspect implements Aspect{
/**
* Method that should be called before real method
*
* #param MethodInvocation $invocation Invocation
* #Before("execution(General->*(*))")
*
*/
public function beforeMethodExecution(MethodInvocation $invocation)
{
$obj = $invocation->getThis();
echo 'Calling Before Interceptor for method: ',
is_object($obj) ? get_class($obj) : $obj,
$invocation->getMethod()->isStatic() ? '::' : '->',
$invocation->getMethod()->getName(),
'()',
' with arguments: ',
json_encode($invocation->getArguments()),
PHP_EOL;
}
}
As you know, go-aop isn't a PHP extension, so it couldn't transform classes that were loaded directly via require or include. Internally it tries to overwrite the source code on-the-fly, but it should receive a control (via integration with composer or custom autoloader class).
So, you have an error here:
//6 Include test class
include(__DIR__.'/class/class1.php');
You explicitly load this class into memory and there is no way to transform it from userland. To pass a control to the framework, you should make this explicitly. Look at the line AopComposerLoader.php#L99 to have an idea how it works. Here we include a source file via the stream source filter that pass control to the framework and it can transform the class to weave an aspects.
To fix your example just change an include to the following:
include (FilterInjectorTransformer::rewrite(__DIR__.'/class/class1.php'));

Categories