Unit Testing REST Update with Laravel and Mockery - php

I can't seem to figure out how I unit test the update of my controller. i'm getting the following error:
method update() from Mockery_0_App.... Should be called exactly 1 times but called 0 times.
After I remove the if statement in the update (after checking if the allergy exists), I get the following error on the line where I add the id the the unique validation rule:
Trying to get property of on object
My Code:
Controller:
class AllergyController extends \App\Controllers\BaseController
{
public function __construct(IAllergyRepository $allergy){
$this->allergy = $allergy;
}
...other methods (index,show,destroy) ...
public function update($id)
{
$allergy = $this->allergy->find($id);
//if ($allergy != null) {
//define validation rules
$rules = array(
'name' => Config::get('Patient::validation.allergy.edit.name') . $allergy->name
);
//execute validation rules
$validator = Validator::make(Input::all(), $rules);
$validator->setAttributeNames(Config::get('Patient::validation.allergy.messages'));
if ($validator->fails()) {
return Response::json(array('status' => false, 'data' => $validator->messages()));
} else {
$allergy = $this->allergy->update($allergy, Input::all());
if ($allergy) {
return Response::json(array('status' => true, 'data' => $allergy));
} else {
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Create failed! Please contact the site administrator or try again!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
//}
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Cannot update the allergy!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
TestCase:
class AllergyControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->allergy = $this->mock('App\Modules\Patient\Repositories\IAllergyRepository');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testIndex()
{
$this->allergy->shouldReceive('all')->once();
$this->call('GET', 'api/allergy');
$this->assertResponseOk();
}
...Other tests for Index and Show ...
public function testUpdate()
{
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$input = array('name' => 'bar');
$this->allergy->shouldReceive('find')->with(1)->once();
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergy->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();
}
}
Config validation rules file:
return array(
'allergy' => array(
'add' => array(
'name' => 'required|unique:Allergy'
),
'edit' => array(
'name' => 'required|unique:Allergy,name,'
),
'messages' => array(
'name' => 'Name'
)
)
);
Is there a way to actually mock the value provided into the validation rule? Or what is the best way to solve this?

I changed my code to this and now it works! :)
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$allergyObj = Mockery::mock('stdClass');
$allergyObj->name = 1;
$input = array('name' => 'bar');
$this->allergyRepo->shouldReceive('find')->with(1)->once()->andReturn($allergyObj);
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergyRepo->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();

Related

Laravel test class property not persisting

I'm trying to set a sensorId property that will be used by every method in my feature test. The problem is that it is not persisting between tests.
Here is my test class:
class LogApiPostTest extends TestCase
{
public $sensorId;
public function setUp(): void
{
parent::setUp();
$this->sensorId = 'SEN' . rand(10000000, 999999999);
}
public function test_creates_sensor()
{
$response = $this->post('/api/logs', $this->data([
//
]));
$response->assertStatus(200);
$response->assertSeeText('Sensor created');
}
public function test_updates_sensor()
{
$response = $this->post('/api/logs', $this->data([
//
]));
$response->assertStatus(200);
$response->assertSeeText('Sensor updated');
}
public function test_creates_car()
{
$response = $this->post('/api/logs', $this->data([
'CarID' => 'B',
]));
$response->assertStatus(200);
$response->assertSeeText('Car created');
}
public function test_updates_car()
{
$response = $this->post('/api/logs', $this->data([
'CarID' => 'B',
]));
$response->assertStatus(200);
$response->assertSeeText('Car updated');
}
public function test_is_unauthorized()
{
$response = $this->post('/api/logs', $this->data([
'token_id' => 'thisShouldntWork',
]));
$response->assertUnauthorized();
}
public function data($merge = [])
{
return array_merge([
'token_id' => config('api.token'),
'sensorID' => $this->sensorId,
'CarID' => 'A',
'LogType' => 'CarLog',
'Time' => time(),
'DrctnFlr' => '-02',
'MCSS' => 'STB',
'OpMode' => 'INA',
'DoorStats' => '][**',
'DoorCmd' => 'C6:C1>',
'OCSS' => 'GTN02',
'Load' => '002%',
], $merge);
}
}
I just want the sensorId property to persist for all tests in this class.
When there is a dependency between tests, you must use the #depends annotation.
This allows to ensure the execution order of tests is correct and also to pass values between tests. Instead of making sensorId to persist, just pass it from test to test.
https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html#test-dependencies

CodeIgniter 4 redirect()->to() not working on IE

I am getting error from IE when I redirect to "dashboard" controller after settings session values in "login" function ( return redirect()->to(base_url('dashboard'));). I have this working on Chrome, Firefox, Edge, and Opera.
I am using public $sessionDriver = 'CodeIgniter\Session\Handlers\DatabaseHandler'; for session storage. this works well with other borwsers.
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Models\UserModel;
class User extends BaseController
{
public function login()
{
$data = [];
if ($this->request->getMethod() == 'post') {
$rules = [
'email' => 'required|min_length[6]|max_length[50]|valid_email',
'password' => 'required|min_length[8]|max_length[255]|validateUser[email,password]',
];
$errors = [
'password' => [
'validateUser' => "Email or Password don't match",
],
];
if (!$this->validate($rules, $errors)) {
return view('login', [
"validation" => $this->validator,
]);
} else {
$model = new UserModel();
$user = $model->where('email', $this->request->getVar('email'))
->first();
// Stroing session values
$this->setUserSession($user);
// Redirecting to dashboard after login
return redirect()->to(base_url('dashboard'));
}
}
return view('login');
}
private function setUserSession($user)
{
$data = [
'id' => $user['id'],
'name' => $user['name'],
'phone_no' => $user['phone_no'],
'email' => $user['email'],
'isLoggedIn' => true,
];
session()->set($data);
return true;
}
public function register()
{
$data = [];
if ($this->request->getMethod() == 'post') {
//let's do the validation here
$rules = [
'name' => 'required|min_length[3]|max_length[20]',
'phone_no' => 'required|min_length[9]|max_length[20]',
'email' => 'required|min_length[6]|max_length[50]|valid_email|is_unique[tbl_users.email]',
'password' => 'required|min_length[8]|max_length[255]',
'password_confirm' => 'matches[password]',
];
if (!$this->validate($rules)) {
return view('register', [
"validation" => $this->validator,
]);
} else {
$model = new UserModel();
$newData = [
'name' => $this->request->getVar('name'),
'phone_no' => $this->request->getVar('phone_no'),
'email' => $this->request->getVar('email'),
'password' => $this->request->getVar('password'),
];
$model->save($newData);
$session = session();
$session->setFlashdata('success', 'Successful Registration');
return redirect()->to(base_url('login'));
}
}
return view('register');
}
public function profile()
{
$data = [];
$model = new UserModel();
$data['user'] = $model->where('id', session()->get('id'))->first();
return view('profile', $data);
}
public function logout()
{
session()->destroy();
return redirect()->to('login');
}
}
CodeIgniter4 has its "user agent class" this should help you to be able to validate if you are using IE, I share the documentation and I hope it helps you.
You can validate using that class and redirect with another method.
https://codeigniter.com/user_guide/libraries/user_agent.html

(laravel) how to use unique validation request on update

I want to use unique validation in Lavavel 8 but the problem is that it doesn't allow me to update when I don't change the name field
my update code on my TemplateController
public function updateTemplate($templateId, TemplateRequest $templateRequest)
{
$thumbnailUrl = $this->templateService->updateThumbnail($templateRequest);
$this->templateRepository->updateTemplateInfo($templateRequest, $templateId, $thumbnailUrl);
return redirect()->route('templates.list.show', [$templateId])
->with(["message" => __('templates.edit.success')]);
}
this is my UpdateTemplateInfo at TemplateRepository
public function updateTemplateInfo($request, $templateId, $thumbnail)
{
$template = $this->getTemplate($templateId);
$template->name = $request->name;
$template->thumbnail = $thumbnail;
$template->business_type_id = $request->business_type;
$template->update();
}
and this is my TemplateRequest
public function rules()
{
return [
'name' => 'required|unique:templates',
'business_type' => 'required'
];
}
this is the method in web.php
Route::patch('/list/{templateId}/update', 'TemplateController#updateTemplate')->name('templates.list.update');
When I try to update without change the name field, the validator fails
If you don't want to create a separate request for post and put you can do something like this...
public function rules() {
$rules = [
'name' => 'required|unique:templates,name',
'business_type' => 'required'
];
if ($this->method() === 'PUT') {
$rules['name'] .= ',' . $this->route('templateId');
}
return $rules;
}
you should create a new request class for the update case
public function rules()
{
return [
'name' => "required|unique:templates,name,{$this->name}",
'business_type' => 'required'
];
}
or you can edit the current request class to this:
public function rules()
{
$rules = [
'business_type' => 'required'
];
if (request()->method == 'PUT') {
$rules['name'] = "required|unique:templates,name,{$this->name}";
} else {
$rules['name'] = 'required|unique:templates';
}
return $rules;
}

Required option of the filter deactivates validation in ZF3

In ZF3 I created a form with two fields: text and url. Only one of them may be filled out by user and at least one must be filled out.
Imagine: one can put the contents of the site or the url of the site. The form may be used to grab certain data from the site or text.
I prepared two validator classes. One for each input. The classes were getting the input value of the other one from context parameter. The StringLength validator was used for both fields.
This worked almost fine but the bad issue was coming when both fields were submitted empty. Then the data did pass the validation while it should no.
At the case of this issue the fields have required turned to false.
When I switched them to true both of fields got required but I wanted only one to be required.
So the goal is that when both fields were empty the validation result would get false. Then the only one message should appear. I mean the message more or less like this: One of fields must be filled out. Not the 'required' message.
Here you are the form class and both validator classes.
<?php
namespace Application\Filter;
use Application\Form\Test as Form;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'validators' => [
['name' => Url::class],
],
]);
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Text implements ValidatorInterface
{
protected $stringLength;
protected $messages = [];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['url'])) {
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(5000);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
}
public function getMessages()
{
return $this->messages;
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Url implements ValidatorInterface
{
const ERROR_NOT_ALLOWED_STRING = 'string-not-allowed';
protected $stringLength;
protected $messages = [
self::ERROR_NOT_ALLOWED_STRING => 'Only one of text and url field may by filled.',
];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['text'])) {
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(500);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
}
public function getMessages()
{
return $this->messages;
}
}
Update
I used advises from #Crisp and had to do some correction in the code. Added returns and message handling. The working code is below:
<?php
namespace Application\Filter;
use Application\Form\Test as Form;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Url::class],
],
]);
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Text implements ValidatorInterface
{
protected $stringLength;
protected $messages = [];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['url'])) {
if (empty($value)) return false;
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(5000);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
return true;
}
public function getMessages()
{
return $this->messages;
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Url implements ValidatorInterface
{
const ERROR_NOT_ALLOWED_STRING = 'string-not-allowed';
const ERROR_EMPTY_FIELDS = 'empty-fields';
protected $stringLength;
protected $messages = [
self::ERROR_NOT_ALLOWED_STRING => 'Only one of text and url field may be filled out.',
];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['text'])) {
if (empty($value)) {
$this->messages = [
self::ERROR_EMPTY_FIELDS => 'One of the fields must be filled out.',
];
return false;
}
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(500);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
return true;
}
public function getMessages()
{
return $this->messages;
}
}
To ensure your validators always run, even for an empty value, you need to add the allow_empty and continue_if_empty options to your input specs. Otherwise validation is skipped for any value that isn't required.
The following combination should work
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Url::class],
],
]);
}
}
That combination should ensure your validators are applied when empty values are encountered.
Rob Allen (#akrabat) wrote a useful blog post detailing the combinations which is worth bookmarking akrabat.com/zend-input-empty-values/

Update Profile yii framework

i want to build a update profile page, and i have 2 mysql table 1. account 2. info, so i want to insert or update the user info table.
this is what i have so far, but its return an error when saving the data "Call to undefined method stdClass::save()"
controller:
public function actionUpdate_profile() {
$model = new UserProfileForm();
$user = Userinfo::model()->findByPk(Yii::app()->user->id);
$model->name = $user->_name;
$model->myurl = $user->myurl;
if (isset($_POST['UserProfileForm'])) {
$model->attributes = $_POST['UserProfileForm'];
if ($model->validate()) {
$model->attributes = $_POST['UserProfileForm'];
$user->name = $model->name;
$user->myurl = $model->myurl;
$user->save();
});
});
$this->render('update_profile', array(
'model' => $model,
'user' => $user,
));
}
model:
class Userinfo extends CActiveRecord {
public static function model($className = __CLASS__) {
return parent::model($className);
}
public function tableName() {
return 'user_info';
}
public function rules() {
return array(
array('email, name, myurl', 'length', 'max' => 255),
);
}
public function attributeLabels() {
return array(
'user_id' => Yii::t('yii', 'Id User'),
'name' => Yii::t('yii', 'First Name'),
'myurl' => Yii::t('yii', 'Last Name'),
);
}
class UserProfileForm extends CFormModel {
public $name;
public $myurl;
public function rules() {
return array(
array('name, myurl', 'required'),
);
}
public function attributeLabels() {
return array(
'name' => Yii::t('yii', 'First Name'),
'myurl' => Yii::t('yii', 'Last Name'),
);
}
}
I know this not best practice but it is concise -
if($user = Userinfo::model()->findByPk(Yii::app()->user->id)) {
//then work with $user here
}

Categories