I'm new to test, I'm using codeception and phpunit to do some TDD.
However, my methods have a lot of code. Do I used best practices ? Is there a way to improve the readiness of my code, can it be more clean ?
class NewsFormHandlerTest extends \Codeception\Test\Unit
{
/**
* #var \UnitTester
*/
protected $tester;
protected function _before()
{
}
protected function _after()
{
}
private function getFormMock(){
return $this->getMockBuilder(FormInterface::class)
->disableOriginalConstructor()
->getMock();
}
private function getNewsManagerMock(){
return $this->getMockBuilder(INewsManager::class)
->disableOriginalConstructor()
->getMock();
}
// tests
public function testShouldHandleASuccessfulFormSubmissionForAddANews()
{
// prepare
$request = new \Symfony\Component\HttpFoundation\Request();
$news = new News();
$form = $this->getFormMock();
$form->expects($this->once())
->method('isValid')
->will($this->returnValue(true));
$form->expects($this->once())
->method('submit');
$form->expects($this->once())
->method('getData')
->will($this->returnValue($news));
$newsManager = $this->getNewsManagerMock();
$newsManager->expects($this->once())
->method('add');
$user = Stub::make(WebserviceUser::class, []);
// test
$handler = new NewsFormHandler($newsManager, $user);
$newsReturned = $handler->handle($form, $request, NewsFormHandler::ADD);
// assert
$this->assertInstanceOf(News::class, $newsReturned);
$this->assertEquals($news, $newsReturned);
}
public function testShouldHandleASuccessfulFormSubmissionForEditANews()
{
// prepare
$request = new \Symfony\Component\HttpFoundation\Request();
$news = new News();
$form = $this->getFormMock();
$form->expects($this->once())
->method('isValid')
->will($this->returnValue(true));
$form->expects($this->once())
->method('submit');
$form->expects($this->once())
->method('getData')
->will($this->returnValue($news));
$newsManager = $this->getNewsManagerMock();
$newsManager->expects($this->once())
->method('edit');
$user = Stub::make(WebserviceUser::class, []);
// test
$handler = new NewsFormHandler($newsManager, $user);
$newsReturned = $handler->handle($form, $request, NewsFormHandler::EDIT);
// assert
$this->assertInstanceOf(News::class, $newsReturned);
$this->assertEquals($news, $newsReturned);
}
public function testFailFormWithInvalidData()
{
// prepare
$request = new \Symfony\Component\HttpFoundation\Request();
$form = $this->getFormMock();
$form->expects($this->once())
->method('isValid')
->will($this->returnValue(false));
$newsManager = $this->getNewsManagerMock();
$newsManager->expects($this->never())
->method('edit');
$this->expectException(InvalidFormException::class);
$user = Stub::make(WebserviceUser::class, []);
// test
$handler = new NewsFormHandler($newsManager, $user);
$newsReturned = $handler->handle($form, $request, NewsFormHandler::ADD);
// assert
$this->assertNull($newsReturned);
}
}
You can probably extract body of testShouldHandleASuccessfulFormSubmissionForAddANews and testShouldHandleASuccessfulFormSubmissionForEditANews to other method with parameter like $action = 'add'|'edit' (or use your defined contants NewsFormHandler::EDIT etc), as they're almost the same.
You can extract mock creation from above methods to a single parametrized method, as the process is almost the same (pass the differences as method arguments and let it do the dirty work).
You can also add some readability by using BDD style, as in example in page http://codeception.com/docs/05-UnitTests#BDD-Specification-Testing
Related
I'm writing this test:
public function update_emails_in_gmail(): void
{
$emailId = new EmailId(self::EMAIL_ID);
$sender = new EmailAddress(self::SENDER_ADDRESS);
$sentAt = new DateTimeImmutable();
$email = new Email($emailId, $sender, $sentAt);
$labelToAdd = new Label(self::LABEL_ADD_THIS, self::LABEL_ADD_THIS);
$labelToRemove = new Label(self::LABEL_REMOVE_THIS, self::LABEL_REMOVE_THIS);
$removeLabel = new RemoveLabel($labelToRemove);
$addLabel = new AddLabel($labelToAdd);
$this->gmailService
->expects($this->once())
->method('modifyMessage')
->with($this->equalTo($emailId), $this->equalTo([$addLabel, $removeLabel]))
;
$email
->addLabel($labelToAdd)
->removeLabel($labelToRemove)
;
$this
->gmailRepository
->updateEmail($email);
}
And this is the code for updateEmail:
/**
* #param Email $email
* #return void
*/
public function updateEmail(Email $email): void
{
$this
->gmail
->modifyMessage($email->id(), [new AddLabel(new Label("", "")), new RemoveLabel(new Label("", ""))]);
}
I was expecting the test to fail, since self::LABEL_ADD_THIS and self::LABEL_REMOVE_THIS are both not empty but phpUnit is saying the test passed...
Any idea what I'm doing wrong?
I'm new with symfony and I'm trying to view data from one of my tables with random order and a limit of 4. I tried doing it in the repository but RAND() is not working so I'm trying in the controller.
The error is the following one:
"Warning: array_rand() expects parameter 1 to be array, object given"
And I don't understand why, when in the $response I set the data into an array.
This is my actual code:
/**
* #Route("/ws/random/superviviente", name="ws_random_survi")
*/
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$response = new JsonResponse();
$response -> setStatusCode(200);
$response -> setData(array('random perk' => $randomPerks));
$resultRandom = array_rand($response);
return $resultRandom;
}
You are trying to use array_rand on a doctrine array collection.
You could either convert it as array and back to a doctrine array :
use Doctrine\Common\Collections\ArrayCollection;
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$resultRandom = new ArrayCollection(array_rand($randomPerks->toArray()));
return new JsonResponse($resultRandom);
}
Otherwise it would work with shuffle :
$randomPerks = $entityManager->getRepository(Perks::class)->getRandomPerks();
$randomPerks = shuffle($randomPerks);
Or get random perks directly through your method in your repository.
See example from #Krzysztof Trzos:
public function getRandomProducts($amount = 7)
{
return $this->getRandomProductsNativeQuery($amount)->getResult();
}
/**
* #param int $amount
* #return ORM\NativeQuery
*/
public function getRandomProductsNativeQuery($amount = 7)
{
# set entity name
$table = $this->getClassMetadata()
->getTableName();
# create rsm object
$rsm = new ORM\Query\ResultSetMapping();
$rsm->addEntityResult($this->getEntityName(), 'p');
$rsm->addFieldResult('p', 'id', 'id');
# make query
return $this->getEntityManager()->createNativeQuery("
SELECT p.id FROM {$table} p ORDER BY RAND() LIMIT 0, {$amount}
", $rsm);
}
You could write your own query to achieve this, so create a new method inside the repository like so:
public function getRandomPerks(int $limit): array
{
$queryBuilder = $this->createQueryBuilder('p');
return $queryBuilder
->setMaxResults($limit)
->orderBy('RAND()')
->getQuery()
->getResult();
}
Then in your controller all you would to do is call the method and pass a limit:
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks(4);
I have learnt Symfony2 since several months.
I have created a service. When I use it in a simple controller, I have no problem. When I use it in my controller that manages my entity, I have a problem.
My service is:
<?php
namespace Cours\BlogBundle\Services;
class Service1
{
public function creerSlug($texte)
{
$texte = transliterator_transliterate("Latin-ASCII; [:Punctuation:] Remove; Lower();", $texte);
$texte = preg_replace('/[-\s]+/', '-', $texte);
$texte = trim($texte, '-');
return $texte;
}
}
My simple controller is:
<?php
namespace Cours\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Cours\BlogBundle\Services\Service1;
class TestController extends Controller
{
public function indexAction()
{
$texte = "Le test du slug";
$service1 = $this->container->get('service1');
$texte = $service1->creerSlug($texte);
return $this->render('CoursBlogBundle:Test:index.html.twig', array('texte' => $texte));
}
}
The action of my controller that manages my entity is:
public function ajouterAction(Request $request)
{
$rubrique = new Rubrique();
$form = $this->createForm(new RubriqueType(), $rubrique);
if ($request->isMethod('POST'))
{
$form->handleRequest($request);
if ($form->isValid())
{
$manager = $this->getDoctrine()->getManager();
$rubrique = $form->getData();
$texte = $rubrique->getTexte();
$service1 = $this->container->get('service1');
$slug = $serviceSlug->creerSlug($texte);
$slug = $rubrique->setSlug($slug);
$manager->persist($rubrique);
$manager->flush();
return $this->redirect($this->generateUrl('cours_blog_accueil'));
}
}
return $this->render('CoursBlogBundle:Rubrique:ajouter.html.twig', array('form' => $form->createView()));
}
My view tells me that my slug can’t be empty.
I think there is a mistake in my action but I can’t find it.
Does anyone help me?
KISSES AND THANK YOU VERY MUCH
change
$service1 = $this->container->get('service1');
$slug = $serviceSlug->creerSlug($texte);
to
$serviceSlug = $this->container->get('service1');
$slug = $serviceSlug->creerSlug($texte);
I suggest to comment/remove $rubrique = $form->getData(); $texte = $rubrique->getTexte(); from ajouterAction and set the text manually (just for testing purpose):
if ($form->isValid())
{
$manager = $this->getDoctrine()->getManager();
// $rubrique = $form->getData();
// $texte = $rubrique->getTexte();
$rubrique->setTexte('Some text');
$service1 = $this->get('service1');
$slug = $service1->creerSlug($rubrique->getTexte());
...
if it works, you can set some validations to the texte field in your form type which prevents entering an invalid value.
Also I suggest to use some libraries (Cocur/Slugify can be a good option) instead of handling the process by yourself.
I have created an annotation class called #Module, and a command GenerateModulesCommand. What I want is to find all controller actions that have the #Module annotation.
Example :
/**
*
* #Module(name='sidebar', enabled=true')
*/
public function sidebarAction($name) {
$ape = new ArrayParamsExtension ();
return $this->render('ModuleManagerBundle:Default:sidebar.html.twig', $ape->getArrayParams($name));
}
I want to be able to look at the specific properties in the #Module (name, enabled, etc...)
So far, this is my execute method from my Command :
protected function execute(InputInterface $input, OutputInterface $output) {
$path = $this->getApplication()->getKernel()->locateResource('#ModuleManagerBundle');
$driver = new PHPDriver($path);
$classes = $driver->getAllClassNames();
foreach ($classes as $key => $class) {
$reader = new AnnotationReader();
$annotationReader = new CachedReader(
$reader, new ArrayCache()
);
$reflClass = new ReflectionClass("\Controller\\" . $reportableClass);
$annotation = $annotationReader->getClassAnnotation(
$reflClass, 'Custom_Annotation'
);
if (is_null($annotation)) {
unset($classes[$key]);
}
}
$output->writeln($path);
}
I found this code on sof, but I don't know how to search all Controller classes and all the Actions inside them..
You can try this in your function
use Doctrine\Common\Annotations\AnnotationReader;
your function
public function getControllersWithAnnotationModules()
{
$allAnnotations = new AnnotationReader();
$controllers = array();
foreach ($this->container->get('router')->getRouteCollection()->all() as $route) {
$defaults = $route->getDefaults();
if (isset($defaults['_controller'])) {
$controllerAction = explode(':', $defaults['_controller']);
$controller = $controllerAction[0];
if (!isset($controllers[$controller]) && class_exists($controller)) {
$controllers[$controller] = $controller;
}
}
}
$controllersWithModules = array();
foreach($controllers as $controller){
$reflectionClass = new \ReflectionClass($controller);
$module = $allAnnotations->getClassAnnotation($reflectionClass,'Acme\YourBundle\Module');
if($module)
$controllersWithModules[] = $controller;
}
return $controllersWithModules ;
}
I am trying to test a method on the bind event of a custom form type.
Here is the code
public function bind(DataEvent $event)
{
$form = $event->getForm();
if($form->getNormData() instanceof BaseFileInterface && !$event->getData() instanceof UploadedFile) {
$event->setData($form->getNormData());
}
if($event->getData() instanceof UploadedFile) {
$hander = $this->handlerManager->getHandler(
$form->getParent()->getConfig()->getDataClass(),
$form->getName()
);
$datas = $hander->createBaseFile($event->getData());
$event->setData($datas);
}
if(is_string($event->getData())) {
$event->setData(null);
}
}
and the form builder of the type :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventSubscriber(new BaseFileListener($this->handlerManager))
->addViewTransformer(new BaseFileToStringTransformer())
;
}
My test class inherits from Symfony\Component\Form\Tests\FormIntegrationTestCase and is doing this :
protected function setUp()
{
parent::setUp();
$this->handlerManager = $this->getHandlerManagerMock();
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
$this->form = $this->factory->create('my_file');
$this->form->setParent($this->getFormMock())->setData(new DummyEntity());
}
protected function getFormMock()
{
$mock = $this
->getMockBuilder('Symfony\Component\Form\Tests\FormInterface')
->disableOriginalConstructor()
->getMock()
;
return $mock;
}
public function testBindHandleNewFileWithNonEmptyField()
{
$data = $file = new UploadedFile(
__DIR__.'/../Fixtures/test.gif',
'original.gif',
'image/gif',
filesize(__DIR__.'/../Fixtures/test.gif'),
null
);
$event = new FormEvent($this->form, $data);
$listener = new BaseFileListener($this->handlerManager);
$listener->bind($event);
$this->assertInstanceOf('My\FooBundle\Entity\BaseFileInterface', $event->getData());
$this->assertNotEquals($event->getData(), $this->form->getNormData());
}
The probleme is that the $form->getParent()->getConfig()->getDataClass() throws an exception on getDataClass ().
The question is how to build the form correctly to perform the bind test ?
Ok, answering my self :)
Here is the good mocking in phpunit :
protected function setUp()
{
parent::setUp();
$this->handlerManager = $this->getHandlerManagerMock();
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
$this->form = $this->factory->create('my_file');
$this->form->setParent($this->getFormMock());
}
protected function getFormMock()
{
$mock = $this->getMock('Symfony\Component\Form\Tests\FormInterface');
$mock
->expects($this->any())
->method('getConfig')
->will($this->returnValue($this->getFormConfigMock()))
;
return $mock;
}
protected function getFormConfigMock()
{
$mock = $this->getMockBuilder('Symfony\Component\Form\FormConfigInterface')
->disableOriginalConstructor()
->getMock();
$mock
->expects($this->any())
->method('getDataClass')
->will($this->returnValue('My\FooBundle\Tests\DummyEntity'))
;
return $mock;
}
I thought I could manage to rebuild the entire form without using mock, but it seems impossible.
If someone has a better solution to offer I'm interested.