I am trying to add unit test for my forms.
The problem is that almost all my forms has other types like collection, entity types etc.
I do that to mock Entity Type:
/**
* #return \PHPUnit_Framework_MockObject_MockObject
*/
protected function mockEntityType()
{
$mockEntityManager = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$mockRegistry = $this->getMockBuilder('Doctrine\Bundle\DoctrineBundle\Registry')
->disableOriginalConstructor()
->setMethods(array('getManagerForClass'))
->getMock();
$mockRegistry->expects($this->any())->method('getManagerForClass')
->will($this->returnValue($mockEntityManager));
$mockEntityType = $this->getMockBuilder('Symfony\Bridge\Doctrine\Form\Type\EntityType')
->setMethods(array('getName'))
->setConstructorArgs(array($mockRegistry))
->getMock();
$mockEntityType->expects($this->any())->method('getName')
->will($this->returnValue('entity'));
return $mockEntityType;
}
Then to use this in a test i do that for example:
public function testCompanyType()
{
$formData = array(
'name' => 'Test name',
'description' => 'Test description',
);
$company = new Company();
$this->fromArray($company, $formData);
$type = new CompanyType();
$form = $this->factory->create($type);
// submit the data to the form directly
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($company->getName(), $form->getData()->getName());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this->assertArrayHasKey($key, $children);
}
}
The problem is that in line $form = $this->factory->create($type);
I get error:
The option "route_name" does not exist. Defined options are: "action",
"attr", "auto_initialize", "block_name", "by_reference",
"choice_attr", "choice_label", "choice_list", "choice_loader",
"choice_name", "choice_translation_domain", "choice_value", "choices",
"choices_as_values", "class", "compound", "data", "data_class",
"disabled", "em", "empty_data", "empty_value", "error_bubbling",
"expanded", "group_by", "id_reader", "inherit_data", "label",
"label_attr", "label_format", "loader", "mapped", "max_length",
"method", "multiple", "pattern", "placeholder",
"post_max_size_message", "preferred_choices", "property",
"property_path", "query_builder", "read_only", "required",
"translation_domain", "trim", "virtual".
It mocks somehow entity type but with wrong options. EntityType should have route_name.
Anyone can help me fix this?
Related
I want to make crud in postman with codeigniter4. But I have a problem when I will add data with post function an error occurs in postman like the picture I sent.
This is a picture of the Error section
controller Konven.php
class Konven extends BaseController
{
/**
* Get all Konven
* #return Response
*/
public function index()
{
$model = new KonvenModel();
return $this->getResponse(
[
'message' => 'Product Convent retrieved',
'produk_konven' => $model->findAll()
]
);
}
/**
* Create a new KOnven
*/
public function store()
{
$rules = [
'nama_menu' => 'required',
'tipe_id' => 'required'
];
$input = $this->getRequestInput($this->request);
if (!$this->validateRequest($input, $rules)) {
return $this->getResponse(
$this->validator->getErrors(),
ResponseInterface::HTTP_BAD_REQUEST
);
}
$konvenEmail = $input['nama_menu'];
$model = new KonvenModel();
$model->save($input);
$produk_konven = $model->where('nama_menu', $konvenEmail)->first();
return $this->getResponse(
[
'message' => 'Product Convent added successfully',
'produk_konven' => $produk_konven
]
);
}
/**
* Get a single client by ID
*/
public function show($produk_konven_id)
{
try {
$model = new KonvenModel();
$produk_konven = $model->findKonvenById($produk_konven_id);
return $this->getResponse(
[
'message' => 'Product Convent retrieved successfully',
'produk_konven' => $produk_konven
]
);
} catch (Exception $e) {
return $this->getResponse(
[
'message' => 'Could not find product convent for specified ID'
],
ResponseInterface::HTTP_NOT_FOUND
);
}
}
model KonvenModel.php
class KonvenModel extends Model
{
protected $table = 'produk_konven';
protected $allowedFields = [
'nama_menu',
'status',
'tipe_id'
];
protected $updatedField = 'updated_at';
public function findKonvenById($produk_konven_id)
{
$produk_konven = $this
->asArray()
->where(['produk_konven_id' => $produk_konven_id])
->first();
$produk_konven->join('tipe', 'produk_konven.tipe_id = tipe.tipe_id');
$produk_konven->orderBy("produk_konven_id", "asc");
if (!$produk_konven) throw new Exception('Could not find client for specified ID');
return $produk_konven;
}
}
Can friends help me overcome the obstacles that I am experiencing?
In a Symfony 3.3 project I try to test this simple form:
class FooFormType extends AbstractType
{
private $fooService;
public function __construct(FooService $fooService)
{
$this->fooService = $fooService;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'bar',
EntityType::class,
[
'class' => Bar::class,
'choice_label' => 'title',
'placeholder' => 'Please select a bar',
]
)
->add(
'baz',
ChoiceType::class,
[
'choices' => $this->fooService->lorem(),
]
)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Foo::class,
]
);
}
}
I followed the instruction I found at http://symfony.com/doc/current/form/unit_testing.html#testings-types-from-the-service-container to write this (simplified) test:
class FooFormTypeTest extends TypeTestCase
{
protected function getExtensions()
{
$fooServiceDummy = $this->createMock(FooService::class);
$fooFormType = new FooFormType($fooServiceDummy);
$managerRegistryDummy = $this->createMock(ManagerRegistry::class);
$entityFormType = new EntityType($managerRegistryDummy);
return [
new PreloadedExtension([$fooFormType, $entityFormType], []),
];
}
/**
* #test
*/
public function submitValidData()
{
$form = $this->factory->create(FooFormType::class);
}
}
Unfortunatelly this exception is thrown:
Symfony\Component\Form\Exception\RuntimeException: Class "AppBundle\Entity\Bar" seems not to be a managed Doctrine entity. Did you forget to map it?
What is the problem here?
I want to test a controller using a mock.
In my controller
public function myAction() {
$email = new MandrillApi(['template_name'=>'myTemplate']);
$result = $email
->subject('My title')
->from('no-reply#test.com')
->to('dest#test.com')
->send();
if ( isset($result[0]['status']) && $result[0]['status'] === 'sent' )
return $this->redirect(['action' => 'confirmForgotPassword']);
$this->Flash->error(__("Error"));
}
In test
public function testMyAction() {
$this->get("users/my-action");
$this->assertRedirect(['controller' => 'Users', 'action' => 'confirmForgotPassword']);
}
How do I mock the class MandrillApi ? thank you
In your controller-test:
public function controllerSpy($event){
parent::controllerSpy($event);
if (isset($this->_controller)) {
$MandrillApi = $this->getMock('App\Pathtotheclass\MandrillApi', array('subject', 'from', 'to', 'send'));
$this->_controller->MandrillApi = $MandrillApi;
$result = [
0 => [
'status' => 'sent'
]
];
$this->_controller->MandrillApi
->method('send')
->will($this->returnValue($result));
}
}
The controllerSpy method will insert the mocked object once the controller is setup correctly. You don't have to call the controllerSpy method, it gets executed automatically at some point after you make the $this->get(... call in your test.
Obviously you have to change the App\Pathtotheclass-part of the mock-generation to fit the location of your MandrillApi-class.
I have an entity, call it Stones and Stones has a ManyToMany relationship with Attributes.
So I query the entity to get the Stones and then I hydrate this to convert it into an array.
$result = $this->stoneRepository->find($stone_id);
if ( ! $result )
{
return false;
}
$resultArray = $this->doctrineHydrator->extract($result);
This works fine for the Stone entity however I noticed that the join (Attributes) remain as objects.
array (size=12)
'id' => int 1
'name' => string 'Agate' (length=5)
'title' => string 'Title' (length=5)
'attribute' =>
array (size=5)
0 =>
object(Stone\Entity\StAttribute)[1935]
private 'id' => int 2
private 'name' => string 'Hay fevor' (length=9)
private 'state' => boolean true
private 'created' => null
private 'modified' => null
1 =>
object(Stone\Entity\StAttribute)[1936]
private 'id' => int 15
private 'name' => string 'Libra' (length=5)
private 'state' => boolean true
private 'created' => null
private 'modified' => null
2 =>
etc.
What is the process to hydrate the Attribute objects?
Hydration is populating an object (entity) using an array which is opposite of the extraction.
Since you want the resultset in array format, you should prevent unnecessary hydration and extraction process which already occurs in the ORM level under the hood.
Try to use Query Builder Api instead of built-in find() method of the entity repository. This is not a single-line but really straightforward and faster solution, it should work:
$qb = $this->stoneRepository->createQueryBuilder('S');
$query = $qb->addSelect('A')
->leftJoin('S.attribute', 'A')
->where('S.id = :sid')
->setParameter('sid', (int) $stone_id)
->getQuery();
$resultArray = $query->getOneOrNullResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
This way, you will also prevent running additional SQL queries against database to fetch associated entities. (StAttribute in your case)
I thought I would follow up on this to show how this can be resolved using a CustomStrategy.
By far the easiest and fastest method was suggested by foozy. What I like about the solution is that when I use hydration in ApiGility for instance I can build custom queries which will produce the desired result in a very few lines of code.
The other solution I was working on was to add a custom strategy:
<?php
namespace Api\V1\Rest\Stone;
use DoctrineModule\Stdlib\Hydrator\Strategy\AbstractCollectionStrategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class CustomStrategy extends AbstractCollectionStrategy
{
public function __construct($hydrator)
{
$this->hydrator = $hydrator;
}
/**
* #param mixed $values
* #return array|mixed
*/
public function extract($values)
{
$returnArray = [];
foreach ($values AS $value)
{
$returnArray[] = $this->hydrator->extract($value);
}
return $returnArray;
}
/**
* #param mixed $values
* #return mixed
*/
public function hydrate($values)
{
$returnArray = [];
foreach ($values AS $value )
{
$returnArray[] = $this->hydrator->hydrate($value);
}
return $returnArray;
}
}
Then from the service side I add various strategies to the hydrator like so:
$result = $this->stoneRepository->find($stone_id);
$this->doctrineHydrator->addStrategy("product", new CustomStrategy( $this->doctrineHydrator ) );
$this->doctrineHydrator->addStrategy("attribute", new CustomStrategy( $this->doctrineHydrator ) );
$this->doctrineHydrator->addStrategy("image", new CustomStrategy( $this->doctrineHydrator ) );
$this->doctrineHydrator->addStrategy("related", new CustomStrategy( $this->doctrineHydrator ) );
$resultArray = $this->doctrineHydrator->extract($result);
After which I created a custom entity:
<?php
namespace Api\V1\Rest\Stone;
class StoneEntity
{
public $id;
public $name;
public $description;
public $code;
public $attribute;
public $product;
public $image;
public function getArrayCopy()
{
return array(
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'code' => $this->code,
'attribute' => $this->attribute,
'product' => $this->product,
'image' => $this->image
);
}
public function exchangeArray(array $array)
{
$this->id = $array['id'];
$this->name = $array['name'];
$this->description = $array['description'];
$this->code = $array['code'];
$this->attribute = $array['attribute'];
$this->product = $array['product'];
$this->image = $array['image'];
}
}
And the final part is to exchange the returned data with the custom entity:
$entity = new StoneEntity();
$entity->exchangeArray($resultArray);
And finally to return the result:
return $entity;
To be honest, the above is just too long winded and my final solution as per the suggestion by foozy was this:
public function fetchOne($stone_id)
{
$qb = $this->stoneRepository->createQueryBuilder('S');
$query = $qb->addSelect('A','P','I','C')
->leftJoin('S.attribute', 'A')
->innerJoin('A.category', 'C')
->innerJoin('S.product' , 'P')
->innerJoin('S.image' , 'I')
->where('S.id = :sid')
->setParameter('sid', (int) $stone_id)
->getQuery();
$resultArray = $query->getOneOrNullResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
if ( ! $resultArray )
{
return false;
}
return $resultArray;
}
I want to test a model and for one of those tests I want to mock a method of the model I am testing. So I don't test a controller and I don't want to replace a whole model, just one method of the same model I test.
Reason is that this model method calls a file upload handler. This feature is already tested elsewhere.
What I am doing now is:
I test the model 'Content'. There I test it's method 'addTeaser', which calls 'sendTeaser'.
SO I want to mock sendTeaser and fake a successful answer of the method sendTeaser, while testing addTeaser.
That looks like this:
$model = $this->getMock('Content', array('sendTeaser'));
$model->expects($this->any())
->method('sendTeaser')
->will($this->returnValue(array('ver' => ROOT.DS.APP_DIR.DS.'webroot/img/teaser/5/555_ver.jpg')));
$data = array(
'Content' => array(
'objnbr' => '555',
'name' => '',
...
)
)
);
$result = $model->addTeaser($data);
$expected = true;
$this->assertEquals($expected, $result);
When I let my test run, I get an error that a model within the method 'sendTeaser' is not called properly. Hey! It shouldn't be called! I mocked the method!
..... or not?
What would be the proper syntax for mocking the method?
Thanks a lot as always for help!
Calamity Jane
Edit:
Here is the relevant code for my model:
App::uses('AppModel', 'Model');
/**
* Content Model
*
* #property Category $Category
*/
class Content extends AppModel {
public $dateipfad = '';
public $fileName = '';
public $errormessage = '';
public $types = array(
'sqr' => 'square - more or less squarish',
'hor' => 'horizontal - clearly wider than high',
'lnd' => 'landscape - low but very wide',
'ver' => 'column - clearly higher than wide',
);
public $order = "Content.id DESC";
public $actsAs = array('Containable');
public $validateFile = array(
'size' => 307200,
'type' => array('jpeg', 'jpg'),
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
public $hasMany = array(
'CategoriesContent' => array(
'className' => 'CategoriesContent',
),
'ContentsTag' => array(
'className' => 'ContentsTag',
),
'Description' => array(
'className' => 'Description',
)
);
/**
* Saves the teaser images of all formats.
*
* #param array $data
*
* #return Ambigous <Ambigous, string, boolean>
*/
public function addTeaser($data)
{
$objnbr = $data['Content']['objnbr'];
$type = $data['Content']['teaser-type'];
if (!empty($data['Content']['teaser-img']['tmp_name'])) {
$mFileNames = $this->sendTeaser($data, $objnbr, $type);
}
if (!is_array($mFileNames)) {
$error = $mFileNames;
//Something failed. Remove the image uploaded if any.
$this->deleteMovedFile(WWW_ROOT.IMAGES_URL.$mFileNames);
return $error;
}
return true;
}
/**
* Define imagename and save the file under this name.
*
* Since we use Imagechache, we don't create a small version anymore.
*
* #param integer $objnbr
* #param string $teasername
*
* #return multitype:Ambigous <string, boolean> |Ambigous <boolean, string>
*/
public function sendTeaser($data, $objnbr, $type)
{
//$path = str_replace('htdocs','tmp',$_SERVER['DOCUMENT_ROOT']);
$this->fileName = $this->getImageName($objnbr, $type);
$oUH = $this->getUploadHandler($data['Content']['teaser-img']);
debug($oUH);
exit;
$error = $oUH->handleFileUpload();
if (empty($type))
$type = 0;
if ($error === 'none'){
// Send to ImageChacheServer
$oICC = $this->getImagecacheConnector();
$sCacheUrl = $oICC->uploadFile($objnbr, $type, $this->fileName);
debug($sCacheUrl);
return array($type => $this->fileName);
}
return $error;
}
public function getUploadHandler($imgdata)
{
App::uses('UploadHandler', 'Lib');
$oUH = new UploadHandler($this, $imgdata);
return $oUH;
}
}
Changing getMock to getMockForModel didn't change the output though.
I'd like to emphasize the answer from #ndm using Cake test helper class CakeTestCase::getMockForModel()
$theModel = CakeTestCase::getMockForModel('Modelname', ['theMethodToMock']);
$theModel->expects($this->once())
->method('theMethodToMock')
->will($this->returnValue('valueToReturn'));
$this->getMock is not the way to mock. You should use $this->generate
I would reccomend you to read a book about CakePHP unti testing, like this: https://leanpub.com/cakephpunittesting