I try to do a functional test on a controller that executes doctrine. When I execute my test, it fails. but when I commented in my controller this line:"$products = $em->getRepository("Couture\FrontBundle\Entity\Produit")->findAll()".
my test is success.
this is my controller:
class ProductController extends Controller {
/**
* Get products
* #Route("/products")
* #Method("GET")
*/
public function getAllAction() {
$serialize = $this->get('jms_serializer');
$em = $this->getDoctrine();
$products = $em->getRepository('Couture\FrontBundle\Entity\Produit')->findAll();
if (!$products) {
$response = new Response(json_encode(array('error' => 'Resources not found for products')));
$response->headers->set('Content-Type', 'application/json');
$response->setStatusCode('400');
return $response;
}
$response = new Response($serialize->serialize($products, 'json'));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
this is my class test:
class ProductControllerTest extends WebTestCase {
public function testGetAll() {
$client = static::createClient();
$client->request('GET', $client->getContainer()->get('router')->generate('couture_front_product_getall'));
$this->assertEquals(
200, $client->getResponse()->getStatusCode()
);
}
}
When you comment that line, !$products is false and then your controller return a Reponse object with application/json content-type header. This response is considered as successful because its status code is 200 OK.
What I have seen in such scenario, is the use of data fixtures to test entities managed by doctrine entityManager.
Your test class may look like this ( not tested code, just to give you the idea behind):
class ProductControllerTest extends WebTestCase {
public function testGetAll() {
$client = static::createClient();
$fixtures = array('Acme\YourBundle\dataFixtures\LoadProductData');
$this->loadFixtures($fixtures);
$uri= $this->getUrl('couture_front_product_getall');
$crawler= $client->request('GET',$uri);
$this->assertEquals(200,$client->getResponse()->getStatusCode() );
}
}
Related
I have a CrudController for my entity, Participant. I want to add a custom action, sendAcknowledgementEmail. The EasyAdmin docs doesn't mention anything about the custom function parameters or return values.
I have the following code
public function configureActions(Actions $actions): Actions
{
$send_acknowledgement_email = Action::new('sendAcknowledgementEmail', 'Send Acknowledgement Email', 'fa fa-send')
->linkToCrudAction('sendAcknowledgementEmail');
return $actions
->add(Crud::PAGE_INDEX, $send_acknowledgement_email)
->add(Crud::PAGE_EDIT, $send_acknowledgement_email)
;
}
public function sendAcknowledgementEmail() //Do I need parameters?
{
//How do I get the Entity?
//What should I return?
}
So far, EasyAdmin detects the custom function but I get an error "The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned null. Did you forget to add a return statement somewhere in your controller?"
How do I continue from here?
The v3.x of the bundle is quite new and the documentation is not perfect yet.
Based on Ceochronos answer, here is my implementation for a clone action.
public function configureActions(Actions $actions): Actions
{
$cloneAction = Action::new('Clone', '')
->setIcon('fas fa-clone')
->linkToCrudAction('cloneAction');
return $actions
->add(Crud::PAGE_INDEX, $cloneAction);
}
public function cloneAction(AdminContext $context)
{
$id = $context->getRequest()->query->get('entityId');
$entity = $this->getDoctrine()->getRepository(Product::class)->find($id);
$clone = clone $entity;
// custom logic
$clone->setEnabled(false);
// ...
$now = new DateTime();
$clone->setCreatedAt($now);
$clone->setUpdatedAt($now);
$this->persistEntity($this->get('doctrine')->getManagerForClass($context->getEntity()->getFqcn()), $clone);
$this->addFlash('success', 'Product duplicated');
return $this->redirect($this->get(CrudUrlGenerator::class)->build()->setAction(Action::INDEX)->generateUrl());
}
After browsing through the EasyAdmin AbstractCrudController I came up with the following working code.
In order to get the current object you need the parameter AdminContext
For my use case I want to return to the CrudController index action, so for that I can do a redirect.
Note: you need to inject the CrudUrlGenerator service in your constructor controller.
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
// Your logic
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current function looks like this:
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
$participant->sendAcknowledgementEmail();
$this->addFlash('notice','<span style="color: green"><i class="fa fa-check"></i> Email sent</span>');
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current working code
<?php
namespace App\Controller\Admin;
use App\Service\WebinarService;
use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator;
use Symfony\Contracts\Translation\TranslatorInterface;
// ...
class ParticipantCrudController extends AbstractCrudController
{
private CrudUrlGenerator $crudUrlGenerator;
private WebinarService $webinar_service;
private TranslatorInterface $translator;
public function __construct(CrudUrlGenerator $crudUrlGenerator, WebinarService $webinar_service, TranslatorInterface $translator)
{
$this->crudUrlGenerator = $crudUrlGenerator;
$this->webinar_service = $webinar_service;
$this->translator = $translator;
}
// ...
public function sendAcknowledgementEmail(AdminContext $context): Response
{
$participant = $context->getEntity()->getInstance();
try {
$this->webinar_service->sendAcknowledgementEmail($participant);
$this->addFlash('notice', 'flash.email.sent');
} catch (Exception $e) {
$this->addFlash('error', $this->translator->trans('flash.error', ['message' => $e->getMessage()]));
}
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl()
;
return $this->redirect($url);
}
}
First I will explain why and how the solution works and then the problems I have encountered. If you think there is a better way to do what I do, I'd love to hear it. I would also like to know why doctrine behaves in this way.
It turns out that my aplication needs to connect to a different database according to the client. I have a table, in a fixed database, containing the connection information that is used in some request.
I have had success with the following code:
class DynamicEntityManager {
protected $em;
private $request;
private $client_id;
public function __construct(RequestStack $request, EntityManagerInterface $em){
$this->em = $em;
$this->request = $request;
}
public function getEntityManager(ClientConn $client = null) {
$request = $this->request->getCurrentRequest();
if($client == NULL){
$domain = $request->attributes->get('domain');
if($domain == "" || $domain == NULL){
throw new \Exception("Error de conexion", 1);
}
$client = $this->em->getRepository(ClientConn::class)->findOneBy(array(
"subdomain" => $domain
));
if($client == NULL){
throw new \Exception("Error de conexion", 1);
}
}
$connectionDB = $client->getConnection();
$dbdriver = 'oci8';
$conexionSplit = explode(':',$connectionDB);
$dbhost = $conexionSplit[0];
$dbport = $conexionSplit[1];
$dbname = $conexionSplit[2];
$dbuser = $client->getUsuarioBd();
$dbpass = $client->getClaveBd();
$service = false;
$this->client_id = $client->getId();
if(strpos($dbname,'SN=') !== false){
$parts = explode('=',$dbname);
$dbname = $parts[1];
$service = true;
}
$request->attributes->set('client_id',$client->getId());
$conn = array(
'driver' => $dbdriver,
'host' => $dbhost,
'port' => $dbport,
'dbname' => $dbname,
'user' => $dbuser,
'password' => $dbpass,
'service' => $service,
'charset' => 'UTF8',
'schema' => null
);
return EntityManager::create($conn, $this->em->getConfiguration());
}
}
As you can see I return EntityManager::create($conn, $this->em->getConfiguration ()) with the new connection. The way I use it is the next:
/**
* #Route("/api/client/{id}/conf/{confID}", name="conf.show")
* #Method({"GET"})
*/
public function show(ClientConn $client, Request $request, DynamicEntityManager $dem ,$confId){
try {
$em = $dem->getEntityManager($client);
$entity = $em->getRepository(Configuration::class)->find($confId);
return new JsonResponse($entity, 200);
}
catch(\Exception $ex) {
return new JsonResponse([
"excepcion" => $ex->getMessage()
], $ex->getCode());
}
}
It works as expected or so I believed until I saw that when the entity has a custom repository it is unable to use the dynamic connection and therefore the previous route will return a table not found exception.
#ORM\Entity() <-- Works like a charm
#ORM\Entity(repositoryClass="App\Repository\ConfigurationRepository")<-- Table not found.
It works in the repository if I create the connection again, although I do not like the solution. So, what do I want? I would like to be able to use the basic methods like find (), findBy () and others without having to rewrite them every time I use a custom repository.
class ConfigurationRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry, DynamicEntityManager $dem)
{
parent::__construct($registry, Configuration::class);
$this->dem= $dem;
}
public function uglyFind($client, $confID)
{
$query = $this->dem->getEntityManager($client)->createQueryBuilder('conf')
->select("conf")
->from(ConfPedidosLentes::class,'conf')
->where('conf.id = :value')->setParameter('value', $confID)
->getQuery();
return $query->getOneOrNullResult();
}
I will really appreciate any contribution and thought in this matter.
Instead of:
class ConfigurationRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry, DynamicEntityManager $dem)
{
parent::__construct($registry, Configuration::class);
$this->dem= $dem;
}
...
try extending EntityRepository (without using a constructor) and use find as you did in your controller:
use Doctrine\ORM\EntityRepository;
class ConfigurationRepository extends EntityRepository
{
}
ServiceEntityRepository is an optional EntityRepository base class with a simplified constructor for autowiring, that explicitly sets the entity manager to the EntityRepository base class. Since you have not configured your doctrine managers to handle these connections properly (it's not even possible actually with so many connections), ServiceEntityRepository will pass a wrong EntityManager instance to the EntityRepository subclass, that's why you should not extend ServiceEntityRepository but EntityRepository.
I'm trying to test a form but i got unreachable field exception.
My controller's code :
class StudentController extends Controller
{
/**
* #Route("/student/new",name="create_new_student")
*/
public function newAction(Request $request){
$student = new Student();
$form = $this->createFormBuilder($student)->add('name',TextType::class)
->add('save',SubmitType::class,['label' => 'Create student'])->getForm();
$form->handleRequest($request);
if($form->isSubmitted()){
$student = $form->getData();
$name = $student->getName();
echo "Your name is ".$name;
die();
}
return $this->render(':Student:new.html.twig',['form' => $form->createView()]);
}
}
My StudentControllerTest :
class StudentControllerTest extends WebTestCase
{
public function testNew(){
$client = static::createClient();
$crawler = $client->request('POST','/student/new');
$form = $crawler->selectButton('Create student')->form();
$form['name'] = 'Student1';
$crawler = $client->submit($form);
$this->assertGreaterThan(0,$crawler->filter('html:contains("Your name is Student1")')->count());
}
}
When i run the test using phpunit i got :
InvalidArgumentException: Unreachable field "name"
I'm following the tutorial from https://symfony.com/doc/current/testing.html
You should use the $form['form_name[subject]'] syntax
public function testNew(){
$client = static::createClient();
//you should request it with GET method, it's more close to the reality
$crawler = $client->request('GET','/student/new');
$form = $crawler->selectButton('Create student')->form();
$form['form_name[name]'] = 'Student1';
// [...]
}
Try this way. Edit Test
$form = $crawler->selectButton('Create student')->form(['name' => 'Student1']);
Edit Controller:
...
$name = $student->getName();
return new Response("Your name is ". $name);
Do not kill what Symfony request.
use Goutte\Client;
/**
* code removed
*/
class ......... {
public function xxxxxx() {
$client = new Client();
$client->getClient()->setDefaultOption('config/curl/'.CURLOPT_TIMEOUT, 60);
Gives error "Method setDefaultOption not found in class GuzzleHttp/Client"
Also code completion can not find the method.
Example is from https://github.com/FriendsOfPHP/Goutte
I am indeed on Guzzle~6 so this code worked:
$crawler = $client->request('GET', 'http://www.example.com',
['curl' => ['CURLOPT_TIMEOUT' => 60]]);
How can I test event listener in the Symfony bundle?
I plan to test it with client (send request and get response). But I have no controllers in my bundle. Can I add 'special' controllers and routes from functional test and test output from them?
I've found way how to add controllers for test.
First - create new controller class (I created it in %BundleName%/Tests/Controller)
%BundleName%/Tests/Controller/TestController.php
namespace %BundleName%\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class TestController extends Controller
{
public function rootAction()
{
return new Response('This is home page');
}
public function galleryAction($id)
{
return new Response(sprintf('This is gallery %s', $id));
}
}
Then I used this controller in test.
%BundleName%/Tests/Controller/PageControllerTest.php
namespace %BundleName%\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Routing\Route;
class PageControllerTest extends WebTestCase
{
/**
* #var \Symfony\Bundle\FrameworkBundle\Client
*/
private $client;
public function setUp()
{
$this->client = static::createClient();
$this->client->followRedirects(true);
$this->setUpRoutes();
}
public function testFirst()
{
$crawler = $this->client->request('GET', '/gallery/42');
}
protected function setUpRoutes()
{
$container = $this->client->getContainer();
/** #var \Symfony\Bundle\FrameworkBundle\Routing\Router $router */
$router = $container->get('router');
$collection = $router->getRouteCollection();
foreach ($collection->all() as $routeId => $route) {
//Leave some routes if you need...
$collection->remove($routeId);
}
$controllerClassName = '\%BundleName%\Tests\Controller\TestController';
$rootRoute = new Route('/', array('_controller' => sprintf('%s::%s', $controllerClassName, 'rootAction')));
$galleryRoute = new Route('/gallery/{id}', array('_controller' => sprintf('%s::%s', $controllerClassName, 'galleryAction')));
$collection->add('_test_root_route', $rootRoute);
$collection->add('_test_gallery_route', $galleryRoute);
}
}
On each test start setUpRoutes method clears route list and registers new routes. Each route _controller param value is \%BundleName%\Tests\Controller\TestController::nameOfAction'.