I'm using PHPUnit to create a Unit test for a store function that stores data in a database.
Currently, i have a test verifies that it has stored data.
However, I also want to create a test that proves that a laravel log message has been produced if the model save function fails.
The code below shows the store function. The "log::info" is the line I want to test.
Thanks.
public function store(Venue $venue){
$saved = $venue->save();
if($saved == false){
Log::info('Failed to Save Venue'. $venue);
}
}
This what I have so far, i pass an empty model that will cause the save to fail due to database constraints
public function test_venue_store_failed(){
$venue = new Venue();
$venueRepo = new VenueRepository();
$this->withExceptionHandling();
$venueRepo->store($venue);
}
You can mock the Log facade in your unit test as follows, as per the docs:
public function test_venue_store_failed(){
$venue = new Venue();
Log::shouldReceive('info')
->with('Failed to Save Venue'. $venue);
$venueRepo = new VenueRepository();
$this->withExceptionHandling();
$venueRepo->store($venue);
}
Maybe you can use event listener on Models.
using this you can get logs on Create or other Events.
Check out the Example Below.
Hope to help .
Info 1.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use User;
class Venue extends Model
{
protected $fillable = ['title', 'ect..'];
public static function boot() {
parent::boot();
static::created(function($item) {
\Log::info('venue.created');
});
static::updated(function($item) {
\Log::info('venue.created');
});
static::deleted(function($item) {
\Log::info('venue.created');
});
}
}
Info 2.
Also there is an exists method on model
if ($venue->exists()) {
// saved
} else {
// not saved
}
Info 3
To get the insert queries when $venue->save(); error, you can try to catch the exception like this:
try{
$venue = new Venue;
$venue->fields = 'example';
$venue->save(); // returns false
}
catch(\Exception $e){
// do task when error
\Log::info($e->getMessage()); // insert query
}
Hope this helps :)
Related
I'm working in Laravel 5.1 and saving a gecko to a database. My code for my store method is below:
public function store(GeckoRequest $request)
{
$user_id = Auth::user()->id;
$input = $request->all();
$input['genetics'] = json_encode($input['genetics'], JSON_FORCE_OBJECT);
$input['user_id'] = $user_id;
Gecko::create($input);
$name = str_replace(' ', '-', $request['name']);
flash()->success('Success!', 'Your gecko has been added to the system');
return redirect()->action('GeckoController#show', [$name]);
}
I know I could do $input['uid'] = str_random(10); - But how do I ensure it is in fact unique and won't redirect back to my form if it isn't unique?
Is there a proper practice into achieving something like this?
Create a function that generates the 10 digit random key then passes it through a validator with a unique rule set. If the validator gives you an error re-run the same function to generate a new one
public function randomId(){
$id = \Str::random(10);
$validator = \Validator::make(['id'=>$id],['id'=>'unique:table,col']);
if($validator->fails()){
return $this->randomId();
}
return $id;
}
From what I understand of your question, you could achieve this using a created event on the model. This will allow you to add anything to the model before it is persisted in the database without any further interaction required.
In a service provider, or your App\Providers\AppServiceProvider add the event to the boot method.
public function boot()
{
User::creating(function ($user) {
// When ever a User model is created
// this event callback will be fired.
$user->setAttribute('randomString', str_random(10));
// Returning 'false' here will stop the
// creation of the model.
});
}
Full documentation for eloquent model events.
Here is something I've used before:
do
{
$code = // generate your code
$gecko_code = Gecko::where('code', $code)->first();
}
while(!empty($gecko_code));
I am beginner to Simpletest and facing an issue while creating fixtures. As I am using cakephp 1.3.14 version for my application.
Created fixture with filename complaint_fixture.php
class ComplaintFixture extends CakeTestFixture {
var $name = 'Complaint';
var $import = array('table' => 'complaints', 'records' => true);
// do not truncate movie_stars table between tests
public function truncate($db) {
return null;
}
// do not drop movie_stars table between tests
public function drop($db) {
return null;
}
}
Created test case with name complaint.test.php
App::import('Model', 'Complaint');
class ComplaintTestCase extends CakeTestCase {
var $fixtures = array('app.Complaint');
function setUp($method) {
parent::setUp();
$this->Complaint = & ClassRegistry::init('Complaint');
// load data
$this->loadFixtures('Complaint');
}
function testFixture() {
$numberOfResults = $this->Complaint->find('count');
var_dump($numberOfResults);
}
/*
function testupdateComplaintStatus(){
$result = $this->Complaint->updateComplaintStatus(47,'ACT');
$this->assertEqual($result,1,'Status updated successfully!');
} */
}
As you can see in the above code, a fixture is created with name Complaint and then a test case is being used to load that fixture. So, what I have read on it from developer guide
- we do create a fixture with specifying the fields name and a records set
- load that fixture in test model class.
BUT, what I am looking for is to perform CRUD operations on test data which is being inserted into the test database. And, when I try to do the same with above given script, It starts affecting the production database records instead of test database.
If you see in the above code I have even stopped truncate and drop for test data, yet not able to sort out the issue.
Can anyone let me know what I have missed in the above code?
What is the best way to write a unit test for a class which depends on an Eloquent model with relationships? E.g.
real object (with database). This is easy, but slow.
real object (no database). I can create a new object but I can't see how to set the related models without writing to the database.
mock object. I run into issues using Mockery with Eloquent models (e.g. see this question).
other solutions?
context: I'm using Laravel with Authority RBAC for access control. I want to find the best way to test my access rules in a unit test. Which means I need to pass the user dependencies to Authority during the test.
If you're writing unit tests, you shouldn't ever use a database. Testing against a database would be considered an integration test. Check out Roy Osherove's videos.
To answer your question, (and not having delved into Authority RBAC, I'd do something like this:
// assuming some RBAC class
SomeRBACClass extends RBACBaseClass {
function validate(UserClass $user) {
if (!$roles = $user->getRoles())
{
return false;
}
$allowed = array('admin', 'superadmin');
foreach ($roles as $role) {
if (in_array($role->name, $allowed)) {
return true;
}
}
return false;
}
}
SomeRBACClassTest extends TestCase {
function test_validate_WhenPassedUser_callsGetRolesOnUserWithNoArgs()
{
$rbac = new SomeRBACClass();
$user = Mockery::mock('UserClass');
$user->shouldReceive('getRoles')->once()->withNoArgs();
$rbac->validate($user);
}
function test_validate_getRolesOnUserReturnsCollectionOfRoles_CallsGetAttributeWithNameOnFirstRole() {
$rbac = new SomeRBACClass();
$user = Mockery::mock('UserClass');
// assuming $user->getRoles() returns a collection
$collection = new \Illuminate\Support\Collection(array(
$role1 = Mockery::mock('Role'),
$role2 = Mockery::mock('Role'),
));
$user->shouldReceive('getRoles')->andReturn($collection);
$role1->shouldReceive('getAttribute')->once()->with('name');
$rbac->validate($user);
}
function test_validate_getAttributeWithNameOnRoleReturnsValidRole_ReturnsTrue() {
$rbac = new SomeRBACClass();
$user = Mockery::mock('UserClass');
// assuming $user->getRoles() returns a collection
$collection = new \Illuminate\Support\Collection(array(
$role1 = Mockery::mock('Role'),
$role2 = Mockery::mock('Role'),
));
$user->shouldReceive('getRoles')->andReturn($collection);
$role1->shouldReceive('getAttribute')->andReturn('admin');
$result = $rbac->validate($user);
$this->assertTrue($result);
}
This is not a thorough example of all the unit tests that I would write, but it's a start. E.g., I would also validate that when no roles are returned, that the result is false.
I have a business rule such as this :
If a JobSeeker wants to apply to a Vacancy, make sure that the Resume used in application is completed and that JobSeeker hadn't
applied to that Vacancy already. If the condition is satisfied, then
the application will be written in a JobApplicatiion.
This is what I came up with :
JobSeeker.php
class JobSeeker {
private $applications;
private $resume;
/** Other irrelevant props **/
public function apply(Vacancy $vacancy, Resume $resume) {
// Business rule #1
if(!$resume->isCompleted()) throw new \Exception('Resume '.$resume->getTitle().' is incomplete.');
// Business rule #2
$alreadyApplied = array_filter($this->applications->toArray(), function(JobApplication $application) use($vacancy) {
return $application->getVacancy() === $vacancy;
});
if($alreadyApplied) throw new \Exception('Vacancy '.$vacancy->getTitle().' is already applied');
// If both rules passed, then create a JobApplication
$application = new JobApplication($this, $vacancy, $resume);
$this->applications->add($application);
return $application;
}
}
JobApplication.php
class JobApplication {
private $applicant;
private $vacancy;
private $resume;
public function __construct(JobSeeker $applicant, Vacancy $vacancy, Resume $resume) {
$this->applicant = $applicant;
$this->vacancy = $vacancy;
$this->resume = $resume;
}
}
If I was to expect that everyone would just use
$jobApplication = $jobSeeker->apply($vacancy, $jobSeeker->getResume());
Then there's no problem.
The problem arise when someone do this
$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);
The second example will bypass the business rule validation.
It did occurred to me to separate the rule checking to a different method :
JobSeeker.php
class JobSeeker {
public function canApply() {
// Here goes those 2 business rules mentioned
}
public function apply(Vacancy $vacancy, Resume $resume) {
if($this->canApply($vacancy, $resume)) {
return new JobApplication($this, $vacancy, $resume);
}
}
}
JobApplication.php
class JobApplication {
public function __construct(JobSeeker $jobSeeker, Vacancy $vacancy, Resume $resume) {
if($jobSeeker->canApply($vacancy, $resume)) {
// Same as before
}
}
}
While the second approach guarantees the business rule constraint, it's very redundant and still does not provides the expected result.
$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);
I need an insight in this.
Thanks !
Depending how you do it you have 2 aggregate roots as I see it
JobSeeker
Vacancy
Resume is an like a profile for an user
Well DDD likes to uses services, for almost everything.
So we have the JobSeekerApplicaitonService this services will be used for the external world.
On the JobSeekerApplicaitonService I would add the method apply
public function apply(JobSeeker $jobSeeker, Vacancy $vacancy);
First we check if the bussiness rules are met.
ie.
$jobSeeker->getResume()->isCompleted();
This check throws an error if it is not completed.
Next we make another function at the JobSeekerApplicaitonService which checks if an JobSeeker already has applied, can also be used for the view to let the user already see he has applied for example.
public function hasApplied(JobSeeker $jobSeeker, Vacancy $vacancy);
But this method can now be used in our apply function
$this->hasApplied($jobSeeker, $vacancy);
Again throw an exception when already applied.
You can now savely reutrn the new JobApplication. Although I would say the JobSeekerApplicaitonService repository and create it there, so it is saved in the db because that is what an application service is, a delegator.
Code
class JobSeekerApplicaitonService {
public function apply(JobSeeker $jobSeeker, Vacancy $vacancy) {
if ($jobSeeker->getResume()->isCompleted()) {
// throw exception
} elseif ($this->hasApplied($jobSeeker, $vacancy)) {
// throw exception
}
// save logic or something else you want
}
public function hasApplied(JobSeeker $jobSeeker, Vacancy $vacancy) {
// your check, I would now use the JobApplicationRepository
return false;
}
}
Your first examples of JobSeeker & JobApplication are correct. The JobSeeker.apply method is acting as the factory for JobApplications:
job_application = job_seeker.apply(vacancy, resume)
Looks good.
The following statement, however, doesn't make much sense:
$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);
Considering the real world, have you ever seen a JobApplication randomly burst into existence out of thin air and land on your desk? I haven't :) In most cases, one entity is created from another:
employee = company.hire(person)
invoice = employee.create_invoice(customer, invoice_terms)
etc...
If you see an entity being 'new-ed' in a command handler, it should raise an eyebrow.
I'm trying to run a job queue to create a PDF file using SlmQueueBeanstalkd and DOMPDFModule in ZF".
Here's what I'm doing in my controller:
public function reporteAction()
{
$job = new TareaReporte();
$queueManager = $this->serviceLocator->get('SlmQueue\Queue\QueuePluginManager');
$queue = $queueManager->get('myQueue');
$queue->push($job);
...
}
This is the job:
namespace Application\Job;
use SlmQueue\Job\AbstractJob;
use SlmQueue\Queue\QueueAwareInterface;
use SlmQueue\Queue\QueueInterface;
use DOMPDFModule\View\Model\PdfModel;
class TareaReporte extends AbstractJob implements QueueAwareInterface
{
protected $queue;
public function getQueue()
{
return $this->queue;
}
public function setQueue(QueueInterface $queue)
{
$this->queue = $queue;
}
public function execute()
{
$sm = $this->getQueue()->getJobPluginManager()->getServiceLocator();
$empresaTable = $sm->get('Application\Model\EmpresaTable');
$registros = $empresaTable->listadoCompleto();
$model = new PdfModel(array('registros' => $registros));
$model->setOption('paperSize', 'letter');
$model->setOption('paperOrientation', 'portrait');
$model->setTemplate('empresa/reporte-pdf');
$output = $sm->get('viewPdfrenderer')->render($model);
$filename = "/path/to/pdf/file.pdf";
file_put_contents($filename, $output);
}
}
The first time you run it, the file is created and the work is successful, however, if you run a second time, the task is buried and the file is not created.
It seems that stays in an endless cycle when trying to render the model a second time.
I've had a similar issue and it turned out it was because of the way ZendPdf\PdfDocument reuses it's object factory. Are you using ZendPdf\PdfDocument?
You might need to correctly close factory.
class MyDocument extends PdfDocument
{
public function __destruct()
{
$this->_objFactory->close();
}
}
Try to add this or something similar to the PdfDocument class...
update : it seem you are not using PdfDocument, however I suspect this is the issue is the same. Are you able to regenerate a second PDF in a normal http request? It is your job to make sure the environment is equal on each run.
If you are unable to overcome this problem a short-term quick solution would be to set max_runs configuration for SlmQueue to 1. That way the worker is stopped after each job and this reset to a vanilla state...