A method from my MyClass class I'd like to test looks like this:
public function needs()
{
$domains = $this->em->getRepository(WebDomain::class)->findBy(array(
'client' => $this->client
));
$hosting = $this->em->getRepository(WebHosting::class)->findBy(array(
'client' => $this->client
));
if($domains !== null && $hosting !== null){
return true;
}
return false;
}
Looking at the documentation of Symfony I create a test like this:
public function testNeeds()
{
$em = $this->createMock(ObjectManager::class);
$client = new Client();
/**
* Add WebHosting to Client
*/
$webHosting = new WebHosting();
$webHosting->setClient($client);
/**
* Create a new WebDomain for Client/WebHosting
*/
$webDomain = new WebDomain();
$webDomain->setClient($client);
$webDomain->setWebHosting($webHosting);
I know how to create a mocked repository (the needed $domains for example):
$domains = $this->createMock(ObjectRepository::class);
$domains->expects($this->any())
->method('findBy')
->willReturn($client->getWebDomain());
$em->expects($this->any())
->method('getRepository')
->willReturn($domains);
$myClass = new MyClass($client, $em);
So from my understanding, this creates a mock that whenever the method findBy is called, return the $domains, but what do I have to add in order to return the needed $hosting?
I suspect it has something to do with the $this->any(), I assume I have to narrow it down to expects(WebDomain::class) (which does not work ofc).
Since I am fairly new to UnitTests in Symfony (and in general) pointing me to the right manual might help as well. Thank you!
In you case you should return different Repository based on argument passed to getRepository method. Something like:
$emMock
->method('getRepository')
->will($this->returnValueMap([
[WebDomain::class, $webDomainRepositoryMock),
[WebHosting::class, $webHostingRepositoryMock)
]));
Note: remember to configure findBy for both repositories.
Related
I'm creating my own Normalizer, based on the tutorial on the Symfony documentation page https://symfony.com/doc/current/serializer/custom_normalizer.html, which I find incomplete because it tells you how to create it but not apply it, that's the first point.
Then based on my little experience in Symfony I'm trying to guess how to pass data to the normalizer to be the proper calculations, the data I'm trying to pass are not services, which can be a String or a Request object, but none of this data allows me, really I need to understand or I need to refactor to get what I want?
I put my normalizer code to understand well what I am looking for.
Normalizer:
<?php
namespace App\Serializer;
use App\Entity\Task;
use App\Traits\TaskControl;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class TaskNormalizer implements NormalizerInterface
{
use TaskControl;
private $normalizer;
private $rangeDate;
public function __construct(ObjectNormalizer $normalizer, $rangeDate )
{
$this->normalizer = $normalizer;
$this->rangeDate = $rangeDate;
}
public function normalize($task, $format = null, array $context = [])
{
$data = $this->normalizer->normalize($task, $format, $context);
dd($this->rangeDate);
$data['totalWork'] = $this->timeTask($task,$this->rangeDate);
return $data;
}
public function supportsNormalization($task, $format = null, array $context = []): bool
{
return $task instanceof Task;
}
}
Applying the normalizer:
Passing the variable $rangeDate that is dynamic from object Request.
$rangeDate = $request->request->get('range','all');
$serializer = new Serializer([new TaskNormalizer($normalizer,$rangeDate)]);
$data = $serializer->normalize($attendances, null, ['attributes' => $attributes]);
and this is the error I get:
Cannot autowire service "App\Serializer\TaskNormalizer": argument "$rangeDate" of method "__construct()" has no type-hint, you should configure its value explicitly.
Why would you pass your range date as constructor argument?
Normalizer is a service dependency, rangeDate is a dynamic value.
You can pass it as an argument for the method normalize instead either as a new argument, or in the context array:
$rangeDate = $request->request->get('range','all');
$serializer = new Serializer([new TaskNormalizer($normalizer)]);
$data = $serializer->normalize($attendances, null, ['attributes' => $attributes, 'rangeDate' => $rangeDate]);
You'll have t odeclare your service explicitely... something like this should do the trick:
## services.yml
App\Serializer\TaskNormalizer :
arguments:
$normalizer: '#serializer.normalizer.object' ## check the alias ...
$rangeDate: '%range_date%'
Keep in mind that it is better depend on interface than class, for the sake of dependency inversion principle. So you should think about changing the constructor to :
## your class
public function __construct(NormalizerInterface $normalizer, $rangeDate )
{
$this->normalizer = $normalizer;
$this->rangeDate = $rangeDate;
}
I am net to phpunit in Symfony 3 and I'm wondering what exactly should I follow when testing a repository. For example, I have the following repo function:
/**
* {#inheritdoc}
*/
public function findAdminRole()
{
$role = $this->repository->findOneBy(['name' => Role::ADMIN]);
if (null === $role) {
throw new NotFoundException();
}
return $role;
}
What exactly a test for this would look like? Should I test that the function findOneBy and the NotFoundException are called or to get real data values? I am kinda stuck here.
Thank you.
ok so findOneBY returns either Null or an Object, you can set up sample data which would return either Null or say a role object and test for that, here's something in my opinion to help you get started.
so, in the setup i would mock the repository:
$this->mockRepository = $this
->getMockBuilder('path to the respository')
->disableOriginalConstructor()
->setMethods(array('if you want to stub any'))
->getMock();
$this->object = new class( //this is the class under test
// all your other mocked services, ex : logger or anything else
)
now we have a mock of the repo, lets see how the sample tests would look like
1st test
public function findAdminRoleTestReturnsException(){
$name = ['name' => Role::ABC]; // consider this will return Null result from DB due to whatever reason
$exception = new NotFoundException();
// do whatever other assertions you need here
$this->mockRepository->expects($this->any())
->method('findOneBY')
->with($name)
->will($this->returnValue(null));
// Now call the function
$role = $this->object->findAdminRole();
$this->assertEquals($exception, $role);
}
in the same manner above you can write another test like:
2nd test
public function findAdminRoleTestReturnsNewRole(){
$name = ['name' => Role::ADMIN] // this will find the entry in theDB
$ testRoleObject = new Role();
$this->mockRepository->expects($this->any())
->method('findOneBY')
->with($name)
->will($this->returnValue($testRoleObject));
// Now call the function
$role = $this->object->findAdminRole();
$this->assertEquals($testRoleObject, $role);
}
hope this helps
My controller code:
public function postFilesAction(Request $request)
{
$validator = $this->get('validator');
$requestCredentials = RequestCredentials::fromRequest($request);
$errors = $validator->validate($requestCredentials);
...
validate method in RequestCredentials (Callback constraint).
/**
* #Assert\Callback(payload = {"errorCode" = "FILE_FILE_URL"})
*/
public function validate(ExecutionContextInterface $context)
{
if (! ($this->fileExistsAndValid() || $this->fileUrlExistsAndValid())) {
$context->buildViolation('Neither file nor file_url is present.')->addViolation();
}
}
Callback works as expected, but the value of $constraintViolation->$constraint->$payload is null.
When I'm trying to use payload in other Constraints (NotBlank, for example), it works (I can see it in ConstraintViolation object).
Is it Symfony bug or am I doing somethings wrong? Should I use some other solution to my problem? (I need to check if there's at least one of two fields (file or file_url) present in request).
In Symfony 3.0 you cannot easily access the payload in the callback when using the Callback constraint. Starting with Symfony 3.1, the payload will be passed as an additional argument to the callback (see https://github.com/symfony/symfony/issues/15092 and https://github.com/symfony/symfony/pull/16909).
I managed to solve this problem with following code in the assertion:
/**
* #Assert\Callback(payload = {"error_code" = "1"}, callback = "validate", groups = {"Default", "RequestCredentials"})
*/
public function validate(ExecutionContextInterface $context)
{
// some validation code
}
I think the problem was because of the Symfony Callback constraint constructor:
public function __construct($options = null)
{
// Invocation through annotations with an array parameter only
if (is_array($options) && 1 === count($options) && isset($options['value'])) {
$options = $options['value'];
}
if (is_array($options) && !isset($options['callback']) && !isset($options['groups'])) {
$options = array('callback' => $options);
}
parent::__construct($options);
}
When it is given $options = ['payload' => [...]] (what happened in my case) it turns it into $options = ['callback' => ['payload' => [...]]]
and then '$payload' data becomes inacessable in ConstraintViolation object.
But I'm still not sure whether it's Symfony imperfection or me not getting something and using it wrong.
I would like to call a getter with the stored fieldname from the database.
For example, there are some fieldnames store like ['id','email','name'].
$array=Array('id','email','name');
Normally, I will call ->getId() or ->getEmail()....
In this case, I have no chance to handle things like this. Is there any possibility to get the variable as part of the get Command like...
foreach ($array as $item){
$value[]=$repository->get$item();
}
Can I use the magic Method in someway? this is a bit confusing....
Symfony offers a special PropertyAccessor you could use:
use Symfony\Component\PropertyAccess\PropertyAccess;
$accessor = PropertyAccess::createPropertyAccessor();
class Person
{
private $firstName = 'Wouter';
public function getFirstName()
{
return $this->firstName;
}
}
$person = new Person();
var_dump($accessor->getValue($person, 'first_name')); // 'Wouter'
http://symfony.com/doc/current/components/property_access/introduction.html#using-getters
You can do it like this :
// For example, to get getId()
$reflectionMethod = new ReflectionMethod('AppBundle\Entity\YourEntity','get'.$soft[0]);
$i[] = $reflectionMethod->invoke($yourObject);
With $yourObject being the object of which you want to get the id from.
EDIT : Don't forget the use to add :
use ReflectionMethod;
Hope this helps.
<?php
// You can get Getter method like this
use Doctrine\Common\Inflector\Inflector;
$array = ['id', 'email', 'name'];
$value = [];
foreach ($array as $item){
$method = Inflector::classify('get_'.$item);
// Call it
if (method_exists($repository, $method))
$value[] = $repository->$method();
}
I'm wondering how can I run Symfony 2 command from browser query or from controller.
Its because I don't have any possibility on hosting to run it and every cron jobs are setted by admin.
I don't even have enabled exec() function so when I want to test it, I must copy all content from command to some testing controller and this is not best solution.
See official documentation on this issue for newer versions of Symfony
You don't need services for command execution from controller and, I think, it is better to call command via run method and not via console string input, however official docs suggest you to call command via it's alias. Also, see this answer. Tested on Symfony 2.1-2.6.
Your command class must extend ContainerAwareCommand
// Your command
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
class MyCommand extends ContainerAwareCommand {
// …
}
// Your controller
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
class SomeController extends Controller {
// …
public function myAction()
{
$command = new MyCommand();
$command->setContainer($this->container);
$input = new ArrayInput(array('some-param' => 10, '--some-option' => true));
$output = new NullOutput();
$resultCode = $command->run($input, $output);
}
}
In most cases you don't need BufferedOutput (from Jbm's answer) and it is enough to check that $resultCode is 0, otherwise there was an error.
Register your command as a service and don't forget to call setContainer
MyCommandService:
class: MyBundle\Command\MyCommand
calls:
- [setContainer, ["#service_container"] ]
In your controller, you'll just have to get this service, and call the execute method with the rights arguments
Set the input with setArgument method:
$input = new Symfony\Component\Console\Input\ArgvInput([]);
$input->setArgument('arg1', 'value');
$output = new Symfony\Component\Console\Output\ConsoleOutput();
Call the run method of the command:
$command = $this->get('MyCommandService');
$command->run($input, $output);
In my environment ( Symony 2.1 ) I had to do some modifications to #Reuven solution to make it work. Here they are:
Service definition - no changes.
In controller:
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
...
public function myAction() {
$command = $this->get('MyCommandService');
$input = new ArgvInput(array('arg1'=> 'value'));
$output = new ConsoleOutput();
$command->run($input, $output);
}
You can just simply create an instance of your command and run it:
/**
* #Route("/run-command")
*/
public function someAction()
{
// Running the command
$command = new YourCommand();
$command->setContainer($this->container);
$input = new ArrayInput(['--your_argument' => true]);
$output = new ConsoleOutput();
$command->run($input, $output);
return new Response();
}
Here's an alternative that lets you execute commands as strings the same way you would on the console (there is no need for defining services with this one).
You can check this bundle's controller to see how it's done with all the details. Here I'm going to summarize it ommiting certain details (such as handling the environment, so here all commands will run in the same environment they are invoked).
If you want to just run commands from the browser, you can use that bundle as it is, but if you want to run commands from an arbitrary controller here is how to do it:
In your controller define a function like this:
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\StringInput;
private function execute($command)
{
$app = new Application($this->get('kernel'));
$app->setAutoExit(false);
$input = new StringInput($command);
$output = new BufferedOutput();
$error = $app->run($input, $output);
if($error != 0)
$msg = "Error: $error";
else
$msg = $output->getBuffer();
return $msg;
}
Then you can invoke it from an action like this:
public function dumpassetsAction()
{
$output = $this->execute('assetic:dump');
return new Response($output);
}
Also, you need to define a class to act as output buffer, because there is none provided by the framework:
use Symfony\Component\Console\Output\Output;
class BufferedOutput extends Output
{
public function doWrite($message, $newline)
{
$this->buffer .= $message. ($newline? PHP_EOL: '');
}
public function getBuffer()
{
return $this->buffer;
}
}
same as #malloc
but
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
...
public function myAction() {
$command = $this->get('MyCommandService');
// $input[0] : command name
// $input[1] : argument1
$input = new ArgvInput(array('my:command', 'arg1'));
$output = new ConsoleOutput();
$command->run($input, $output);
}
If you have to pass arguments (and/or options), then in v2.0.12 (and may be true for later versions), you need to specify InputDefinition first before instantiating an input object.
use // you will need the following
Symfony\Component\Console\Input\InputOption,
Symfony\Component\Console\Input\InputArgument,
Symfony\Component\Console\Input\InputDefinition,
Symfony\Component\Console\Input\ArgvInput,
Symfony\Component\Console\Output\NullOutput;
// tell symfony what to expect in the input
$inputDefinition = new InputDefinition(array(
new InputArgument('myArg1', InputArgument::REQUIRED),
new InputArgument('myArg2', InputArgument::REQUIRED),
new InputOption('debug', '0', InputOption::VALUE_OPTIONAL),
));
// then pass the values for arguments to constructor, however make sure
// first param is dummy value (there is an array_shift() in ArgvInput's constructor)
$input = new ArgvInput(
array(
'dummySoInputValidates' => 'dummy',
'myArg2' => 'myValue1',
'myArg2' => 'myValue2'),
$inputDefinition);
$output = new NullOutput();
As a side note, if you are using if you are using getContainer() in your command, then the following function may be handy for your command.php:
/**
* Inject a dependency injection container, this is used when using the
* command as a service
*
*/
function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* Since we are using command as a service, getContainer() is not available
* hence we need to pass the container (via services.yml) and use this function to switch
* between conatiners..
*
*/
public function getcontainer()
{
if (is_object($this->container))
return $this->container;
return parent::getcontainer();
}
You can use this bundle to run Symfony2 commands from controller (http request) and pass options/parameters in URL.
https://github.com/mrafalko/CommandRunnerBundle
If you run a command that need the env option like assetic:dump
$stdout->writeln(sprintf('Dumping all <comment>%s</comment> assets.', $input->getOption('env')));
You have to create a Symfony\Component\Console\Application and set the definition like that:
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOuput;
// Create and run the command of assetic
$app = new Application();
$app->setDefinition(new InputDefinition([
new InputOption('env', '', InputOption::VALUE_OPTIONAL, '', 'prod')
]));
$app->add(new DumpCommand());
/** #var DumpCommand $command */
$command = $app->find('assetic:dump');
$command->setContainer($this->container);
$input = new ArgvInput([
'command' => 'assetic:dump',
'write_to' => $this->assetsDir
]);
$output = new NullOutput();
$command->run($input, $output);
You can't set the option env to the command because it isn't in its definition.