i need some help i want to write a unit test about a controler method , i have searched for examples and tested a lot of method's but none of them has worked:
Here is my controller:
class ComputerController extends Controller
{
/**
* #Route("/list-computers.html", name="back_computer_list")
* #return RedirectResponse|Response
*/
function listComputerAction()
{
$ad = $this->get("ldap_service");
$computers = $ad->getAllComputer();
return $this->render('BackBundle:Computer:list.html.twig', array(
"computers" => $computers,
));
}
I have tried to test it with mock like this:
class ComputerController extends Controller
{
/**
* #var EngineInterface
*/
private $templating;
public function setTemplating($templating)
{
$this->templating = $templating;
}
and i have created a test method:
class ComputerControllerTest extends TestCase {
public function testlistComputerAction(){
$templating = $this->getMockBuilder('BackBundle\Controller\ComputerController')->getMock();
$computers = [1,2];
$templating->expects($this->once())
->method('render')
->with('BackBundle:Computer:list.html.twig', array(
"computers" => $computers))
->will($this->returnValue( $computers));
$controller = new ComputerController();
$controller->setTemplating($templating);
$this->assertEquals('success', $controller->listComputerAction());
}
When i start executing phpunit , i have this warning"Trying to configure method "render" which cannot be configured because it does not exist, has not been specified, is final, or is static"
I would be thankful if someone has an idea about this
I tried to Test a method in ldapService : Here is the method's of the service that i want to test
/**
* #return bool|resource
*/
public function getLdapBind()
{
if (!$this->ldapBind) {
if ($this->getLdapConnect()) {
$this->ldapBind = #ldap_bind($this->ldapConnect, $this->ldapUser, $this->ldapPass);
}
}
return $this->ldapBind;
}
/**
* #param $ldapUser
* #param $password
* #return bool
*/
function isAuthorized($ldapUser, $password)
{
$result = false;
if ($this->ldapConnect) {
$result = #ldap_bind($this->ldapConnect, $ldapUser, $password);
}
return $result;
}
Here is the test (using Mock):
<?php
namespace BackBundle\Tests\Service;
use PHPUnit\Framework\TestCase;
use BackBundle\Service\LdapService;
use PHPUnit_Framework_MockObject_InvocationMocker;
class LdapServiceTest extends TestCase {
public function testgetLdapConnect()
{
// $LdapService = new LdapService();
$ldapMock = $this->getMockBuilder( 'LdapService')->setMethods(['getLdapBind'])->disableOriginalConstructor()->getMock();
$ldapMock->expects($this->once())
// ->method()
->with(array('ldap_bind', 'mike', 'password'))
->will($this->returnValue(true));
$ldapMock->isAuthorized('mike', 'password');
}
}
But i have a warning that i can't resolve : "Method name matcher is not defined, cannot define parameter matcher without one"
If someone , has an idea about that please
Honestly, there is nothing useful to test in that three-line controller. #1 is the service container, and #3 is the Twig subsystem. Line #2 can be unit tested on it's own.
With more complex controllers, I have found that making them a service where all the dependencies are passed in, either by constructor, or into the action itself does make slightly more complex controllers quite easy, but very few need that anyway.
Related
i am building an Api with symfony 4.2 and want to use jms-serializer to serialize my data in Json format, after installing it with
composer require jms/serializer-bundle
and when i try to use it this way :
``` demands = $demandRepo->findAll();
return $this->container->get('serializer')->serialize($demands,'json');```
it gives me this errur :
Service "serializer" not found, the container inside "App\Controller\DemandController" is a smaller service locator that only knows about the "doctrine", "http_kernel", "parameter_bag", "request_stack", "router" and "session" services. Try using dependency injection instead.
Finally i found the answer using the Symfony serializer
it's very easy:
first : istall symfony serialzer using the command:
composer require symfony/serializer
second : using the serializerInterface:
.....//
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
// .....
.... //
/**
* #Route("/demand", name="demand")
*/
public function index(SerializerInterface $serializer)
{
$demands = $this->getDoctrine()
->getRepository(Demand::class)
->findAll();
if($demands){
return new JsonResponse(
$serializer->serialize($demands, 'json'),
200,
[],
true
);
}else{
return '["message":"ooooops"]';
}
}
//......
and with it, i don't find any problems with dependencies or DateTime or other problems ;)
As I said in my comment, you could use the default serializer of Symfony and use it injecting it by the constructor.
//...
use Symfony\Component\Serializer\SerializerInterface;
//...
class whatever
{
private $serializer;
public function __constructor(SerializerInterface $serialzer)
{
$this->serializer = $serializer;
}
public function exampleFunction()
{
//...
$data = $this->serializer->serialize($demands, "json");
//...
}
}
Let's say that you have an entity called Foo.php that has id, name and description
And you would like to return only id, and name when consuming a particular API such as foo/summary/ in another situation need to return description as well foo/details
here's serializer is really helpful.
use JMS\Serializer\Annotation as Serializer;
/*
* #Serializer\ExclusionPolicy("all")
*/
class Foo {
/**
* #Serializer\Groups({"summary", "details"})
* #Serializer\Expose()
*/
private $id;
/**
* #Serializer\Groups({"summary"})
* #Serializer\Expose()
*/
private $title;
/**
* #Serializer\Groups({"details"})
* #Serializer\Expose()
*/
private $description;
}
let's use serializer to get data depends on the group
class FooController {
public function summary(Foo $foo, SerializerInterface $serialzer)
{
$context = SerializationContext::create()->setGroups('summary');
$data = $serialzer->serialize($foo, json, $context);
return new JsonResponse($data);
}
public function details(Foo $foo, SerializerInterface $serialzer)
{
$context = SerializationContext::create()->setGroups('details');
$data = $serialzer->serialize($foo, json, $context);
return new JsonResponse($data);
}
}
I'm getting this error while trying to log in multiple users with guards and unable to understand what instance it needs to be passed:
Argument 1 passed to
Illuminate\Auth\EloquentUserProvider::validateCredentials() must be an
instance of Illuminate\Contracts\Auth\Authenticatable, instance of
App\Employs given, called in /var/www/html/crmproject/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php on line 379
This is my Auth Controller:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class EmploysLoginController extends Controller
{
use AuthenticatesUsers;
protected $guard = 'Employs';
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/Employs';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function showLoginForm()
{
return view('auth.employe-login');
}
public function login(Request $request)
{
if (auth()->guard('Employs')->attempt(['email' => $request->email, 'password' => $request->password])) {
dd(auth()->guard('Employs')->user());
}
return back()->withErrors(['email' => 'Email or password are wrong.']);
}
}
This is my Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Authenticatable;
// use Illuminate\Contracts\Auth\Authenticatable as
AuthenticatableContract;
class Employs extends Model// implements AuthenticatableContract
{
protected $primaryKey = 'employ_id';
}
i tried many solution provided online/stackoverflow but i'm constantly getting this error, and if you find this question has ambiguity please ask before doing down vote i'm trying this out last time here.
You should create a model like this:
Model
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Employs extends Authenticatable
{
use Notifiable;
protected $guard = 'Employs';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
I hope this work for you.
I have a Task entity, with two mandatory, non-nullable, fields:
title
dueDatetime
and Form to create task. The form is called by external scripts through POST with application/x-www-form-urlencoded (so no json or anything fancy), so I use standard symfony to handle this.
Problem is I don't control the scripts, and if the script forgot one of the argument, symfony4 will directly throw an exception at the handleRequest step, before I have the time to check if the form is valid or not. Which result in an ugly response 500.
My question: How to avoid that ? The best for me would be to just continue to use "form->isValid()" as before , but if there's an other standard way to handle that, it's okay too.
Note: it would be best if I don't have to put my entity's setter as accepting null values
The exception I got:
Expected argument of type "DateTimeInterface", "NULL" given.
in vendor/symfony/property-acces /PropertyAccessor.php::throwInvalidArgumentException (line 153)
in vendor/symfony/form/Extension/Core/DataMapper/PropertyPathMapper.php->setValue (line 85)
in vendor/symfony/form/Form.php->mapFormsToData (line 622)
in vendor/symfony/form/Extension/HttpFoundation/HttpFoundationRequestHandler.php->submit (line 108)
in vendor/symfony/form/Form.php->handleRequest (line 492)
A curl that reproduce the error :
curl -d 'title=foo' http://127.0.0.1:8080/users/api/tasks
The code :
Entity:
class Task
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="bigint")
*/
private $id;
/**
* #Assert\NotNull()
* #Assert\NotBlank()
* #ORM\Column(type="string", length=500)
*/
private $title;
/**
*
* #ORM\Column(type="datetimetz")
*/
private $dueDatetime;
public function getDueDatetime(): ?\DateTimeInterface
{
return $this->dueDatetime;
}
public function setDueDatetime(\DateTimeInterface $dueDatetime): self
{
$this->dueDatetime = $dueDatetime;
return $this;
}
public function setTitle($title)
{
$this->title = $title;
return $this;
}
public function getTitle()
{
return $this->title;
}
}
Form
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('dueDatetime')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => Task::class]);
}
}
Controller:
class TaskController extends AbstractController
{
/**
* #Route(
* "/users/api/tasks",
* methods={"POST"},
* name="user_api_create_task"
* )
*/
public function apiCreateTask(Request $request)
{
$task = new Task();;
// the use of createNamed with an empty string is just so that
// the external scripts don't have to know about symfony's convention
$formFactory = $this->container->get('form.factory');
$form = $formFactory->createNamed(
'',
TaskType::class,
$task
);
$form->handleRequest($request); // <-- this throw exception
// but this code should handle this no ?
if (!$form->isSubmitted() || !$form->isValid()) {
return new JsonResponse([], 422);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($task);
$entityManager->flush();
return new JsonResponse();
}
}
There are at least 2 ways to handle this.
In the two ways you will have to add #Assert\NotNull() to the dueDatetime attribute.
1 - You can try/catch the exception of the handleRequest call.[edit] this one breaks the flow, not good.
2 - You can make nullable the setter setDueDatetime(\DateTimeInterface $dueDatetime = null). If you choose this one, please be sure to always validate your entity before an Insert/Update in DB else you will get an SQL error.
In the two cases it will be handled by the validator isValid() and you will have a nice error in your front end.
You need to allow nullable parameter (with "?") in method setDueDatetime
public function setDueDatetime(?\DateTimeInterface $dueDatetime): self
{
$this->dueDatetime = $dueDatetime;
return $this;
}
I have a Symfony 3.2 project with a backend. Each entity has its CRUD Controllers, Views etc. I have prepared an
abstract class AbstractControllerTest extends WebTestCase that is a base for tests for each entity. For each entity I use a simple test that asserts that list, show, edit and new returns HTTP 200.
So when I run all test it test list, show etc for each Entity. The problem is that in list Controller I use KNPPaginator with default order. The Controller works OK but when I run tests and it gets to the second entity I get 500 error because of a missing entity field. It turns out that the test takes a list Query for Pager from previous test.
So Entity A is ordered by default with a position field. Entity B doesn't have position field and that cause the error. So when PHPUnit goes to test A Entity it is OK, then it moves to test B Entity and then there is an error.
I don't know what is going on because ordering is not saved in session so there is no way that PHPUnit gets query from session from previous Entity.
Any ideas what is going on?
AbstractControllerTest
abstract class AbstractControllerTest extends WebTestCase
{
/** #var Client $client */
public $client = null;
protected $user = '';
protected $prefix = '';
protected $section = '';
protected $entityId = '';
public function setUp()
{
$this->client = $this->createAuthorizedClient();
}
/**
* #return Client
*/
protected function createAuthorizedClient()
{
$client = static::createClient();
$client->setServerParameter('HTTP_HOST', $client->getContainer()->getParameter('test_info_domain'));
$client->setServerParameter('HTTPS', true);
$client->followRedirects();
$container = $client->getContainer();
$session = $container->get('session');
/** #var $userManager \FOS\UserBundle\Doctrine\UserManager */
$userManager = $container->get('fos_user.user_manager');
/** #var $loginManager \FOS\UserBundle\Security\LoginManager */
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = $this->section;
/** #var UserInterface $userObject */
$userObject = $userManager->findUserBy(array('username' => $this->user));
$loginManager->logInUser($firewallName, $userObject);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_' . $firewallName,
serialize($container->get('security.token_storage')->getToken()));
$container->get('session')->save();
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
return $client;
}
public function testIndex()
{
//CRUD index
$this->client->request('GET', sprintf('/%s/%s',$this->section,$this->prefix));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
public function testShow()
{
//CRUD show
$this->client->request('GET', sprintf('/%s/%s/%s/show',$this->section,$this->prefix, $this->entityId));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
public function testEdit()
{
//CRUD edit
$this->client->request('GET', sprintf('/%s/%s/%s/edit',$this->section,$this->prefix, $this->entityId));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
public function testNew()
{
//CRUD new
$this->client->request('GET', sprintf('/%s/%s/new',$this->section,$this->prefix));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
}
And an example of one of the test class for Controller for one Entity
class AgendaCategoryControllerTest extends AbstractControllerTest
{
protected $user = 'tom#test.com';
protected $section = 'admin';
protected $prefix = 'agenda-category';
protected $entityId = '40';
}
If I run separately
php phpunit.phar src/Bundle/Tests/Controller/Admin/AControllerTest.php
and
php phpunit.phar src/Bundle/Tests/Controller/Admin/BControllerTest.php
it is OK.
If run together there is this weird bug
php phpunit.phar -c phpunit.xml.dist --testsuite=Admin
You can reset your test client between tests by doing the following in your setUp-method:
public function setUp()
{
$this->client = $this->createAuthorizedClient();
$this->client->restart();
}
You might have to move the restart into your createAuthorizedClient-method to ensure it does not reset your auth info.
First of I all, I created the whole example below specifically for this question because the actual example is very big so if it looks stupid then assume that it is not for now!
I'm trying to come up with a solution so that I can call a correct private method (bankA() or bankB()) in controller if the validation successfully passes. As you can see in the custom validation constraint, I only check the $bank->code property however the condition is not actually that simple (there is repository checks so on) - (as I said above, it is trimmed down version). So, could please someone tell me, how will I know that which private method I should call in controller after successful validation? I'm happy to create dedicated validators if necessary so open for suggestions and examples.
Note: I looked into symfony group validation documentation but didn't really get the picture how I could apply to my scenario.
EXAMPLE REQUEST
{ "id": 66, "code": "A" }
{ "id": 34, "code": "B" }
CONTROLLER
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* #Route("bank", service="application_frontend.controller.bank")
*/
class BankController extends Controller
{
private $validator;
public function __construct(
ValidatorInterface $validator
) {
$this->validator = $validator;
}
/**
* #param Request $request
*
* #Route("")
* #Method({"POST"})
*
* #throws Exception
*/
public function indexAction(Request $request)
{
$content = $request->getContent();
$content = json_decode($content, true);
$bank = new Bank();
$bank->id = $content['id'];
$bank->code = $content['code'];
$errors = $this->validator->validate($bank);
if (count($errors)) {
throw new Exception($errors[0]->getMessage());
}
// OK, validation has passed so which one do I call now ?!?!
$this->bankA($bank);
$this->bankB($bank);
}
private function bankA(Bank $bank)
{
// Do something nice with Bank
}
private function bankB(Bank $bank)
{
// Do something bad with Bank
}
}
BANK MODEL
use Application\FrontendBundle\Validator\Constraint as BankAssert;
/**
* #BankAssert\Bank
*/
class Bank
{
/**
* #var int
*/
public $id;
/**
* #var string
*/
public $code;
}
CUSTOM VALIDATOR
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class Bank extends Constraint
{
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return get_class($this).'Validator';
}
}
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class BankValidator extends ConstraintValidator
{
public function validate($bank, Constraint $constraint)
{
if ($bank->code == 'A') {
return;
}
if ($bank->code == 'B') {
return;
}
$this->context->buildViolation('Bank error')->addViolation();
}
}
Depending on how many codes there are you could either do...
if ('A' === $bank->getCode()) {
$this->bankA($bank);
} else {
$this->bankB($bank);
}
Or..
$method = 'bank'.$bank->getCode();
if (!method_exists($this, $method)) {
throw new \Exception('Method "'.$method.'" does not exist');
}
$this->$method();
All of that being said, it would be advisable to move all of this work into a dedicated service rather than in your controller. Then in your controller use something like...
$this->container->get('do_something_to_bank.service')->processAction($bank);