Phpunit integration testing symfony app - how to inject services into test? - integration-testing

I want to test a class by checking the real database data after function is executed.
I do not understand, how can I inject services which I need, for example some repository class.
So far I have written this:
namespace Tests\integration\Service\JourneyRunner\EmailConditionCheck;
use PHPUnit_Framework_TestCase;
use NG\Model\Journey\EmailConditionCheckRepositoryInterface;
class EmailConditionCheckServiceTest extends PHPUnit_Framework_TestCase
{
private $emailConditionCheckQueueRepository;
public function __construct(
EmailConditionCheckQueueRepositoryInterface $emailConditionCheckQueueRepository
) {
$this->emailConditionCheckQueueRepository;
parent::__construct();
}
public function testPrepareEmailContentForSending()
{
echo 'aaa';
$this->assertEquals(1, 1);
}
}
I added the test service to services.xml
<parameter key="tests.integration.service.journey_runner.email_condition_check.email_condition_check_service_test.class">tests\integration\Service\JourneyRunner\EmailConditionCheck\EmailConditionCheckServiceTest</parameter>
<service id="tests.integration.service.journey_runner.email_condition_check.email_condition_check_service_test" class="%tests.integration.service.journey_runner.email_condition_check.email_condition_check_service_test%">
<argument type="service" id="ng.infrastructure.persistence.time_trigger_queue_repository" />
</service>
I know that argument is wrong currently, but from the error I see that it does not get even the wrong argument - it gets nothing.
PHP Fatal error: Uncaught TypeError: Argument 1 passed to Tests\integration\Service\JourneyRunner\EmailConditionCheck\EmailConditionCheckServiceTest::__construct() must be an instance of Tests\integration\Service\JourneyRunner\EmailConditionCheck\EmailConditionCheckQueueRepositoryInterface, none given, called in /var/www/api.notification.guru/ng-api-service/vendor/phpunit/phpunit/src/Framework/TestSuite.php on line 475 and defined in /var/www/api.notification.guru/ng-api-service/tests/integration/Service/JourneyRunner/EmailConditionCheck/EmailConditionCheckServiceTest.php:12
I tried to search for info, but I cannot find.

Found out how, not inject, but geting a container gives result I want:
<?php
namespace Tests\integration\Service\JourneyRunner\EmailConditionCheck;
use PHPUnit_Framework_TestCase;
use NG\Model\Journey\EmailConditionCheckRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use NG\Model\Uuid;
class EmailConditionCheckServiceTest extends KernelTestCase
{
private $emailConditionCheckQueueRepository;
protected function setUp()
{
self::bootKernel();
$this->container = static::$kernel->getContainer();
$this->emailConditionCheckQueueRepository = $this->container->get('ng.infrastructure.persistence.email_condition_check_repository');
}
public function testPrepareEmailContentForSending()
{
$this->emailConditionCheckQueueRepository->get(Uuid::fromString('1'));
$this->assertEquals(1, 1);
}
}
Also needed to create phpunit.xml - set the app directory. App directory is the one where AppKernel.php lies in your project as I understood.
<?xml version="1.0" ?>
<phpunit>
<php>
<server name="KERNEL_DIR" value="app/" />
</php>
</phpunit>
And pass to phpunit command a parameter
--configuration=phpunit.xml

Related

How to add a new command line option to symfony console

I am not an expert in Symfony, I need to add a new console option --country=XX to the symfony console.
This isnt a command, its an option which changes how whatever command is run, executes by selecting a different database to operate on by building the doctrine.dbal.dbname parameter such as api_fr, api_de, api_es, etc.
I have tried to search for a way to do this, but unfortunately everything comes back to adding commands, which is not what I want to do, I want to add an option.
I am building an API which part of it works with Symfony 2.8 and another part is using Symfony 3.x. I suppose the answer might be the same in both versions, but if you know how to do this in both versions and they are separate, please let me know.
You can add an EventListener like this exemple:
use Symfony\Component\Console\Input\InputOption;
class YourOptionEventListener
{
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$inputDefinition = $event->getCommand()->getApplication()->getDefinition();
// add the option to the application's input definition
$inputDefinition->addOption(
new InputOption('yourOption', null, InputOption::VALUE_OPTIONAL, 'Description of the option', null)
);
}
}
Then add it as a service:
<?xml version="1.0" ?>
<container ...>
<services>
<service id="app_yourOption.console_event_listener"
class="App\YourOptionBundle\EventListener\YourOptionEventListener">
<tag name="kernel.event_listener" event="console.command" method="onConsoleCommand" />
</service>
</services>
</container>
You can check this documentation, in "Add a global command option" chapter, you can find what you need:
http://php-and-symfony.matthiasnoback.nl/2013/11/symfony2-add-a-global-option-to-console-commands-and-generate-pid-file/
The best practice of 2018 and Symfony 3+ is to extend Symfony Application:
<?php
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputOption;
final class SomeApplication extends Application
{
protected function getDefaultInputDefinition()
{
$definition = parent::getDefaultInputDefinition();
$definition->addOption(new InputOption(
'country',
null,
InputOption::VALUE_REQUIRED,
'Country to use'
));
return $definition;
}
}
Then anywhere in your command or service with Symfony\Component\Console\Input\InputInterace service present, just call:
$country = $input->getOption('country');
Do you want to know more?
I've extended the answer in the 4 Ways to Add Global Option or Argument to Symfony Console Application post.

Override TranslationsCacheWarmer service

I have an application that contains a lot of translation resources for a lot of different languages. The warmup process takes a long time because of this.
I only support the translation of my site in a few languages, so I'd like to avoid generating catalogues for all the languages that I don't support.
What I did:
I overrode the TranslationsCacheWarmer to use my own translator. This is a custom translator that decorates the default translator but overrides the warmup method to only warmup files that are part of the locales that I support.
The problem is that the default warmer still runs generating files for all the locales.
This is the code that contains the custom translator: https://gist.github.com/marcosdsanchez/e8e2cd19031a2fbcd894
and here's how I'm defining the services:
<service id="web.translation.public_languages_translator" class="X\Translation\PublicLanguagesTranslator" public="false">
<argument type="service" id="translator.default" />
<argument type="collection">%chess.translation.public_languages%</argument>
</service>
<service id="translation.warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer" public="false">
<argument type="service" id="web.translation.public_languages_translator" />
<tag name="kernel.cache_warmer" />
</service>
I'm using symfony 2.7.3
I ended up doing something different to get the same result. Instead of trying to create a custom CacheWarmer, I created a compiler pass and modified the definition of the 'options' argument. In this compiler pass, I removed all the files that don't have the locale or language code.
Code:
<?php
namespace X\DependencyInjection\Compiler;
use X\Entity\I18nLanguage;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class TranslatorCompilerPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*
* #param ContainerBuilder $container
*
* #api
*/
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('translator.default');
$options = $definition->getArgument(3);
$keys = array_keys($options['resource_files']);
$locales = I18nLanguage::PUBLIC_LOCALES;
$langCodes = array();
foreach (I18nLanguage::PUBLIC_LOCALES as $locale) {
$langCodes[] = substr($locale, 0, strpos($locale, '_'));
}
$localesAndLangCodes = array_merge($locales, $langCodes);
foreach ($keys as $key) {
if (!in_array($key, $localesAndLangCodes, true)) {
unset($options['resource_files'][$key]);
}
}
$arguments = $definition->getArguments();
$definition->setArguments(array($arguments[0], $arguments[1], $arguments[2], $options));
}
}
That did the trick for me and I can also apply other optimizations like the removal of loaders, etc.

symfony functional tests webtestcase doesn't work

I try to write a functional test for my app but I am getting following error:
InvalidArgumentException: The option "test_case" must be set.
I tried googling for it but found literally no clues.
My code:
class WhateverTest extends WebTestCase {
public function testWhatever() {
$client = static::createClient();
$crawler = $client->request('GET', '/home');
$this->assertEquals(1, 1);
}
in phpunit.xml the kernel is set
<php>
<server name="KERNEL_DIR" value="app/" />
</php>
My AppKernel has just two functions: registerBundles() and registerContainerConfiguration
Check which WebTestCase class you are using. Most probably you are using
use Symfony\Bundle\FrameworkBundle\Tests\Functional\WebTestCase
and you should be using
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
So just change this one line and you should be good
you may have mistakenly used
Symfony\Bundle\SecurityBundle\Tests\Functional
or
Symfony\Bundle\FrameworkBundle\Tests\Functional\WebTestCase
instead of
Symfony\Bundle\FrameworkBundle\Test\WebTestCase,

Why I always get the message "You cannot dump a container with parameters" in symfony console?

In Terminal when I try to run my created command I get the following error:
[Symfony\Component\DependencyInjection\Exception\InvalidArgumentException]]
You cannot dump a container with parameters that contain references to other services (reference to service "old_sound_rabbit_mq.split_file_producer" found in "/rabbit").
This is what happens when I run my newly created console command:
$this->getContainer()->get('split_file')->process();
I don't know why it says that You cannot dump! I don't dump anything in the project.
Is there something I'm unaware of?
EDIT
A part of my services.yml:
<parameters>
<parameter key="file_path">/var/www/path/file.xml</parameter>
<parameter key="rabbit" type="service" id="old_sound_rabbit_mq.split_file_producer" />
</parameters>
<service id="split_file" class="Acme\DemoBundle\SplitFile">
<argument>%file_path%</argument>
<argument>%rabbit%</argument>
</service>
And this is my console command class:
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ReadFileCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('reader:read-file');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->getContainer()->get('split_file')->process();
}
}
Parameters only have a key value. You can't add service etc to a parameter.
<service id="split_file" class="Acme\DemoBundle\SplitFile">
<argument>%file_path%</argument>
<argument type="service" id="old_sound_rabbit_mq.split_file_producer"/>
</service>
http://symfony.com/doc/current/book/service_container.html
I'd suggest switching to yml as it's less verbose
You could create an alias for old rabbit if you wanted.

Symfony2 custom console command not working

I created a new Class in src/MaintenanceBundle/Command, named it GreetCommand.php and put the following code in it:
<?php
namespace SK2\MaintenanceBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('maintenance:greet')
->setDescription('Greet someone')
->addArgument('name', InputArgument::OPTIONAL, 'Who do you want to greet?')
->addOption('yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
if ($name) {
$text = 'Hello '.$name;
} else {
$text = 'Hello';
}
if ($input->getOption('yell')) {
$text = strtoupper($text);
}
$output->writeln($text);
}
}
?>
And tried to call it via
app/console maintenance:greet Fabien
But i always get the following error:
[InvalidArgumentException]
There are no commands defined in the "maintenance" namespace.
Any ideas?
I had this problem, and it was because the name of my PHP class and file didn't end with Command.
Symfony will automatically register commands which end with Command and are in the Command directory of a bundle. If you'd like to manually register your command, this cookbook entry may help: http://symfony.com/doc/current/cookbook/console/commands_as_services.html
I had a similar problem and figured out another possible solution:
If you override the default __construct method the Command will not be auto-registered by Symfony, so you have to either take the service approach as mentioned earlier or remove the __construct override and make that init step in the execute method or in the configure method.
Does actually anyone know a good best practice how to do init "stuff" in Symfony commands?
It took me a moment to figure this out.
I figured out why it was not working: I simply forgot to register the Bundle in the AppKernel.php. However, the other proposed answers are relevant and might be helpful to resolve other situations!
By convention: the commands files need to reside in a bundle's command directory and have a name ending with Command.
in AppKernel.php
public function registerBundles()
{
$bundles = [
...
new MaintenanceBundle\MaintenanceBundle(),
];
return $bundles;
}
In addition to MonocroM's answer, I had the same issue with my command and was silently ignored by Symfony only because my command's constructor had 1 required argument.
I just removed it and call the parent __construct() method (Symfony 2.7) and it worked well ;)
If you are over-riding the command constructor and are using lazy-loading/autowiring, then your commands will not be automatically registered. To fix this you can add a $defaultName variable:
class SunshineCommand extends Command
{
protected static $defaultName = 'app:sunshine';
// ...
}
Link to the Symfony docs.
I think you have to call parent::configure() in your configure method
I had this same error when I tried to test my command execution with PHPUnit.
This was due to a wrong class import :
use Symfony\Component\Console\Application;
should be
use Symfony\Bundle\FrameworkBundle\Console\Application;
cf. Other stack thread
In my case it was complaining about the "workflow" namespace although the WorkflowDumpCommand was correctly provided by the framework.
However, it was not available to run because I have not defined any workflows so the isEnabled() method of the command returned false.
I tried to use a service passed via constructor inside the configure method:
class SomeCommand extends Command {
private $service;
public function __construct(SomeService $service) {
$this->service = $service;
}
protected function configure(): void {
$this->service->doSomething(); // DOES NOT WORK
}
}
Symfony uses Autoconfiguration that automatically inject dependencies into your services and register your services as Command, event,....
So first just make sure that you have services.yaml in your config folder. with autoconfigure:true.
this is the default setting
Then Make sure That All your files are exactly the same name as Your Class.
so if you have SimpleClass your file must be SimpleClass.php
If you have a problem because of a __constructor,
go to services.yml and add something like this:
app.email_handler_command:
class: AppBundle\Command\EmailHandlerCommand
arguments:
- '#doctrine.orm.entity_manager'
- '#app.email_handler_service'
tags:
- { name: console.command }
For newer Symfony-Version (5+) commands must be registered as services.
What I do frequently forget while setting it up, is to tag it properly:
<service id="someServiceCommand">
<tag name="console.command"/>
</service>
Without this litte adaptation, your command name will not be displayed and therefore not accessible.

Resources