Cakephp 3 - Unit test validationDefault - unit-testing

I'm currently trying to write a unit test for the following model:
<?php
namespace App\Model\Table;
use App\Model\Entity\User;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Users Model
*
* #property \Cake\ORM\Association\HasMany $Comments
* #property \Cake\ORM\Association\BelongsToMany $Albums
*/
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('users');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Comments', [
'foreignKey' => 'user_id'
]);
$this->belongsToMany('Albums', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'album_id',
'joinTable' => 'users_albums'
]);
}
/**
* #Author: Mark van der Laan
* #Date: 23-02-2016
* #Description: Validating rules for the user model. Some additional, more complex validation rules are added.
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
// id
$validator
->integer('id')
->allowEmpty('id', 'create');
// username
$validator
->requirePresence('username', 'create')
->notEmpty('username')
// Enabled, just in case that the username will be an email address
->email('username')
->add('username', [
'length' => [
'rule' => ['minLength', 7],
'message' => 'Username needs to be at least 7 characters long!',
]
]);
// password
$validator
->requirePresence('password', 'create')
->notEmpty('password')
->add('password', [
'length' => [
'rule' => ['minLength', 7],
'message' => 'Password needs to be at least 7 characters long!',
]
]);
// sign_in_count
$validator
->integer('sign_in_count')
->requirePresence('sign_in_count', 'create')
->notEmpty('sign_in_count');
// ip address
$validator
->allowEmpty('current_sign_in_ip')
->requirePresence('current_sign_in_ip', 'create')
// Currently checking for both IPv4 and IPv6 addresses
->ip('current_sign_in_ip', 'both');
// active
$validator
->boolean('active')
->requirePresence('active', 'create')
->allowEmpty('active');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['username']));
return $rules;
}
}
It is important for me to test the validationDefault method which I try to do with the following code snippet:
public function testValidationDefault()
{
$data = ['username' => 'adminadmin#mixtureweb.nl',
'password' => 'testtest123',
'sign_in_count' => 0,
'current_sign_in_ip' => '127.0.0.1',
'active' => 'true'
];
$this->assertTrue($this->Users->save($data));
// $this->assertTrue($data);
}
As I try to do this, this will throw an error saying that I shouldn't pass an array to assertTrue method. Therefore, I'm trying to find examples but I couldn't find anything. Has anyone some references where I can find how to unit test validation rules? (so far I couldn't find anything in the documentation)
Update
public function testValidationDefault()
{
$data = ['username' => 'adminadmin#mixtureweb.nl',
'password' => 'testtest123',
'sign_in_count' => 0,
'current_sign_in_ip' => '127.0.0.1',
'active' => true
];
$user = $this->Users->newEntity($data);
$saved = $this->Users->save($user);
$this->assertTrue($saved);
// $this->assertTrue($data);
}
This will give 'Failed asserting that App\Model\Entity\User Object &0000000011b3c53b0000000040aca14b is true'. Does anyone know what I'm doing wrong?

Take a look at what Table::save() returns, it's \Cake\Datasource\EntityInterface|bool. On success it returns the persisted entity, on failure it returns boolean false. So your save operation succeeds and it will return an entity, hence the error.
If you want to test validation, you should either use the validator object that your table class offers (Table::validationDefault() via Table::validator()), or use Table::patchEntity() or Table::newEntity() and test the value of Entity:errors().
Patching/creating entities is where validation in the model layer happens, the saving process will only apply application rules.
public function testValidationDefault()
{
$data = [
'username' => 'adminadmin#mixtureweb.nl',
'password' => 'testtest123',
'sign_in_count' => 0,
'current_sign_in_ip' => '127.0.0.1',
'active' => true
];
$user = $this->Users->newEntity($data);
$this->assertEmpty($user->errors()); // empty = no validation errors
}
See also
Cookbook > Validation > Validating Data
Cookbook > Validation > Validating Entities
Cookbook > Database Access & ORM > Validating Data > Validation vs. Application Rules

Related

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'tokenable_id' cannot be null

i want use laravel sanctum with 2 model
this is code for user model
<?php namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens,Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'id', 'name', 'family','gender','birhday','national_code','email','mobile','profie_pic','province_code','city_code','address','username','role','status','password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token','role',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
and usercontroller for function login is :
public function login(Request $request) {
$user= User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response([
'message' => ['These credentials do not match our records.']
], 404);
}
$token = $user->createToken('my-app-token')->plainTextToken;
$response = [
'user' => $user,
'token' => $token
];
return response($response, 201);
}
and token is true and all route with middleware('auth:sanctum') is working true but i want so use model nurces and write this code
first:model nurces:
class Nurces extends model
{
use HasApiTokens,Notifiable;
protected $fillable = [
'name', 'family','gender','birhday','national_code','email','mobile','profie_pic','province_code','city_code','address','username','status','password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password','remember_token',
];
protected $primaryKey = 'nurces_id';
}
and function login in nurcescontroller is :
public function login(Request $request) {
$nurces= Nurces::where('email', $request->email)->first();
if (!$nurces || !Hash::check($request->password, $nurces->password)) {
return response([
'message' => ['These credentials do not match our records.']
], 404);
}
$token = $nurces->createToken('my-nurces-token-')->plainTextToken;
$response = [
'nurces' => $nurces,
'token' => $token
];
return response($response, 201);
}
when use postman and login with this address - this erorr :
"message": "SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'tokenable_id' cannot be null (SQL: insert into `personal_access_tokens` (`name`, `token`, `abilities`, `tokenable_id`, `tokenable_type`, `updated_at`, `created_at`) values (my-nurces-token-, fa55707e2bd9e1b71f8e5ebc0623f9ce1cc8e49f5b6e1ff804dda262e93811f4, [\"*\"], ?, App\\Nurces, 2020-07-05 11:18:58, 2020-07-05 11:18:58))",
I think, there is an mismatch between primary key of App\Nurces model.
As per documentation and error, two columns values of personal_access_tokens table as follows:
tokenable_type - model name e.g
App\Model\User
tokenable_id - primary key of model
In your case, tokenable_id value trying to insert as NULL, that's why getting error.
Please check and confirm primary key column name of App\Nurces model. Add the below property in the Nurces model class.
protected $primaryKey = 'primary key column name';
https://laravel.com/docs/7.x/eloquent

Create custom module for render custom forms through a controller in Drupal 8

I need to render a custom form which is created using Drupal\Core\Form\FormBase and Drupal\Core\Form\FormStateInterface through a controller in custom Drupal 8 module. Is there any guidence or reference to follow to do this?
Actually I tried to render form directly and through a controller. But both ways are not working. Only render the submit button. I refer the drupal 8 documentation also. But I couldn't find a solution for this. Please be kind enough to find my coding samples below. If there are anything wrong. Please correct me.
my_module.routing.yml
partner.content:
path: '/partner'
defaults:
_controller: '\Drupal\partner\Controller\PartnerController::add'
_title: 'Add Partner'
requirements:
_permission: 'access content'
partner.addform:
path: '/partner/add'
defaults:
_form: '\Drupal\partner\Form\AddForm'
_title: 'Add Partner'
requirements:
_permission: 'access content'
AddForm.php
namespace Drupal\my_module\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class AddForm extends FormBase
{
/**
* Returns form id
*
* #return string
*/
public function getFormId(): string
{
return 'my_module_add_form';
}
/**
* Build form array
*
* #param array $form
* #param FormStateInterface $formState
* #return array
*/
public function buildForm(array $form, FormStateInterface $form_state): array
{
// First name
$form['first_name'] = [
'#type' => 'textField',
'#title' => t('First Name'),
'#required' => true,
];
// Other input fields...
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save Changes'),
'#button_type' => 'primary',
);
return $form;
}
public function validateForm(array &$form, FormStateInterface $form_state) {}
public function submitForm(array &$form, FormStateInterface $form_state) {}
}
MyModuleController.php
<?php
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\my_module\Form\AddForm;
class MyModuleController extends ControllerBase
{
public function add()
{
$addForm = new AddForm();
$form = \Drupal::formBuilder()->getForm($addForm);
return [
'#theme' => 'form_my_module_add',
'#form' => $form,
];
}
}
Happy to find out the solution with Hemantha Dhanushka on my comment.
To make it clear this question has a correct answer, here I past the validated comment.
I would recommend you to use the first approach (using routing::_form instead
of Controller). Also, it seems you use the wrong #type for your
first_name field. Try textfield instead of textField.
Also, for people who want to go further, here are some links to implement a proper
routing::_form approach to expose a form as a page instead of using a Controller: https://www.valuebound.com/resources/blog/step-by-step-method-to-create-a-custom-form-in-drupal-8.
For people looking for more help about existing Form Element Reference (textfield, checkboxes, entity_autocomplete, ...) here is an excellent up-to-date article https://drupalize.me/tutorial/form-element-reference?p=2766
You can use buildForm() method for it. Check below code example:
public function add()
{
$form_state = new Drupal\Core\Form\FormState();
$form_state->setRebuild();
$form = \Drupal::formBuilder()->buildForm('Drupal\my_module\Form\AddForm', $form_state);
return [
'#theme' => 'form_my_module_add',
'#form' => $form,
];
}
Reference: https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Form!FormBuilder.php/function/FormBuilder::getForm/8.2.x

Cakephp mock Email Utility

i'm a little stuck trying to test my users-controller in cakephp,
i have an action wich sends an email to a certain email address.
The email utility works, no problem whatsoever.
I want to mock the email utility so that when i test the action (with "testAction"),
no email will be sent.
I already searched all over stackoverflow and tried a lot of solutions, the current code is like follows:
UsersController:
/**
* Get Email Utility, use method so that unit testing is possible
* #return object
*/
public function _getEmailer()
{
return new CakeEmail();
}
public function lostPassword()
{
if($this->request->is('post'))
{
$email = $this->request->data['User']['email'];
$this->User->recursive = -1;
$user = $this->User->find('first', array('conditions' => array('email' => $email)));
if(!$user)
{
$this->Session->setFlash('Error: user not found');
return $this->render();
}
$recoverTrials = $this->User->Recover->find('count', array('conditions' => array('email' => $email)));
if($recoverTrials > 3)
{
$this->Session->setFlash(__('message.recover-too-many'));
return $this->redirect('/');
}
// Generate random key to reset password
$key = md5(microtime().rand());
$data = array('user_id' => $user['User']['id'],
'key' => $key,
'active' => 1,
'created' => date('Y-m-d H:i:s'));
if(!$this->User->Recover->save($data))
{
$this->Session->setFlash('Error while sending the recovery-mail');
return $this->redirect('/');
}
$this->Email = $this->_getEmailer();
$this->Email->emailFormat('html');
$this->Email->to($email);
$this->Email->subject('Password recover');
$this->Email->replyTo('noreply#domain.com');
$this->Email->from(array('noreply#domain.com' => 'Sender ID'));
$this->Email->template('recover');
$this->Email->viewVars(array('key' => $key)); // Set variables for template
try
{
$this->Email->send();
}
catch(Exception $e)
{
$this->Session->setFlash('Error while sending the recovery-mail');
return $this->render();
}
$this->Session->setFlash('The recovery mail with instructions to reset your password has been sent. Please note that the link will only remain active for 2 hours.');
return $this->redirect('/');
}
}
And my test class looks like (excerpt):
public function testPostLostPassword()
{
$this->Controller = $this->generate('Users', array(
'methods' => array(
'_getEmailer'
),
'components' => array('Security')
));
$emailer = $this->getMock('CakeEmail', array(
'to',
'emailFormat',
'subject',
'replyTo',
'from',
'template',
'viewVars',
'send'
));
$emailer->expects($this->any())
->method('send')
->will($this->returnValue(true));
$this->Controller->expects($this->any())
->method('_getEmailer')
->will($this->returnValue($emailer));
Correct email
$data = array('User' => array('email' => 'me#domain.com'));
$result = $this->testAction('/lostPassword', array('method' => 'post',
'data' => $data,
'return' => 'contents'));
}
What am i doing wrong? The Email utility still sends out the email to my address, even though i mocked it...
Thanks in advance!

CakePHP 2.4 mock a method in a model

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

Zend\Form in conjunction with Doctrine 2 and ManyToOne Relationships

today i started reading myself into the features of Zend\Form. I found a great tutorial from Michael Gallego in which he explains how to use some new cool features.
The example works fine so far if we're handling 1-1 Relationships. Doctrine covers them fine.
What i want to do is
Instead of having textarea for the related value, I'd like a select box
The select box should have valid options, depending on what's at the database
For editing purpose later, the currently selected value needs to be selected
Doctrine should not add new rows to the One-Table
As you can see at my github sources i made use of the example in the tutorial, but shortened it to "Product" and "Brand". Brands - in my example - is a DB-Table with predefined Brands (Nike, Adidas, Puma, whatever) and when you create a new Product from the form you get those Brands as a select menu.
Right now, the way i add the options isn't working. I know i can manually set the options with an array like
$form->get('product')->get('brand')->setAttribute('options', array('Nike'=>'1', 'Adidas'=>'2', etc);
But i strongly assume that there is a more automated way to do this. I simply do not understand all this Hydrator classes provided with Zend.
The Problem is, even if i manually define the array as described above, the mapping of Product and Brand is not working correctly. The dump of $product right now looks like this
object(Application\Entity\Product)[210]
protected 'id' => null
protected 'name' => string 'asdasd' (length=6)
protected 'price' => string '123123' (length=6)
protected 'brand' =>
object(Application\Entity\Brand)[215]
protected 'id' => null
protected 'name' => string '1' (length=1)
Obviously the brand is mapped completely wrong (for what i want to achieve, zend probably sees this as right, since the VALUE of my select is 1).
Question How do i tell my Form to map the select-value to the mapped object ID? Though maybe the way i set up my product-model is wrong in that case.
Any help will be greatly appreciated :)
this is code from my form object
I hope it will help
class ProductForm extends Form
{
public function __construct($em)
{
parent::__construct();
$this->add(array(
'name' => 'productGroupId',
'attributes' => array(
'type' => 'select',
'label' => 'Category',
'options' => array(),
),
));
$this->setProductGropus($em->getRepository('Project\Entity\ProductGroup')->findAll());
public function setProductGropus($groups)
{
$groupsForm = array('--Select--'=>'');
foreach ($groups as $group) {
$groupsForm[$group->name] = (string) $group->productGroupId;
}
$this->get('productGroupId')->setAttribute('options',$groupsForm);
}
}
}
Looking at your BrandFieldSet you have only specified name to the InputFilterProvider thus the id will never be passed along.
Secondly im going to recommend you to remove the Registry. Classes created by using the ServiceManager can/should implement ServiceManagareAwareInterface if they need access to anything else if not specified by using constructors.
So in your controller instead of using your registry you access the service manager
$this->getServiceLocator()
->get('FQCN_OR_ALIAS');
There are some great examples written by contributors of the framework and ill list a few of there github repos here.
https://github.com/ZF-Commons And https://github.com/EvanDotPro (Can't post any more since i lack reputation)
Come join us on #zftalk.2 on irc.freenode.org if you have any further questions
Although this is an old question, thought I'd answer anyway. The previous answers don't use the ObjectSelect of Doctrine.
You say to have a OneToOne relationship and do not want records added to the "One-table"; I'm assuming here you have a Uni-directional OneToOne relationship.
However, if you got "Product" and "Brand" as entities a OneToMany Bi-directional relationship might be more suitable ;)
Going however with OneToOne, your entities should look like this:
class Brand {
/**
* #var int
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", nullable=false, length=128)
*/
protected $name;
//Getters/Setters
}
class Product {
/**
* #var int
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", nullable=false, length=128)
*/
protected $name;
//Change below "OneToOne" to "ManyToOne" for proper product + brand relationship. Just this change will leave it as uni-directional.
/**
* #var Brand
* #ORM\OneToOne(targetEntity="Brand", fetch="EAGER")
* #ORM\JoinColumn(name="brand", referencedColumnName="id")
*/
protected $brand;
//Getters/Setters
}
Assuming your entities are correct, you should then use the ObjectSelect build into Doctrine.
class ProductForm
{
/** #var ObjectManager */
protected $objectManager;
public function __construct($name = 'product-form', $options = [])
{
parent::__construct($name, $options);
}
public function init()
{
$this->add([
'type' => 'DoctrineModule\\Form\\Element\\ObjectSelect',
'name' => 'brand',
'required' => true,
'attributes' => [
'id' => 'selectBrand',
'multiple' => false,
'value' => null,
],
'options' => [
'label' => 'Select brand',
'object_manager' => $this->getObjectManager(),
'target_class' => Brand::class,
'property' => 'id',
'is_method' => true,
'find_method' => [
'name' => 'findBy',
'params' => [
'criteria' => [],
'orderBy' => ['name' => 'ASC'],
],
],
'empty_option' => '--- Select Brand ---',
'label_generator' => function (Brand $entity) {
return $entity->getName();
}
],
]);
}
/**
* #return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #param ObjectManager $objectManager
*/
public function setObjectManager(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
}
Make sure to setup the Module.php to be able to load this form. Add the getServiceConfig() function to it.
public function getServiceConfig()
{
/** #var ServiceManager $sm */
return [
'factories' => [
'product_form' => function ($sm)
{
$form = new ProductForm();
$form->setInputFilter(new ProductInputFilter());
/** #var EntityManager $entityManager */
$entityManager = $sm->get('doctrine.entitymanager.orm_default');
//Set Doctrine ObjectManager
$form->setObjectManager($entityManager);
//Set Doctrine Object as Hydrator
$form->setHydrator(new DoctrineObject($entityManager, Product::class));
//Set Doctrine Entity
$form->setObject(new Product());
//Initialize elements onto form
$form->init();
return $form;
},
],
];
}
}
Next, load the form in a Controller.
$form = $this->getServiceLocator()->get('product_form');
===========================
Note: This works up until Zend Framework 2.5.2