How to mock laravel model relathipship? - laravel

I have a model that has a relationship with a View, that is complicate to popolate for make the feature test, but in the same time this is called from some component that are inside the controller called.
The following code is an example:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\TemperatureView;
class Town extends Model
{
function temperature()
{
return $this->hasOne(TemperatureView::class);
}
}
This is an example of the controller:
<?php
namespace App\Http\Controllers;
use App\Models\Town;
class TownController extends Controller
{
public function update($id)
{
// Here is the validation and update of Town model
$UpdatedTown = Town::where('id',$id);
$UpdatedTown->update($data);
$this->someOperation($UpdatedTown);
}
private function someOperation($Town)
{
//Here there is some operation that use the temperature Relationship
/*
Example:
$Town->temperature->value;
*/
}
}
The test is like is like this:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\Models\TownModel;
use Mockery;
use Mockery\MockInterface;
class TownTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_get_town_temperature()
{
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test failed
}
public function test_get_town_temperature_with_mocking()
{
$this->instance(
TownModel::class,
Mockery::mock(TownModel::class, function (MockInterface $mock) {
$MockDataTemperature = (object) array('value'=>2);
$mock->shouldReceive('temperature')->andReturn($MockDataTemperature);
})
);
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test also failed
}
}
The first test failed because the Controller has some check on the relationship temperature, that is empty because the view on database is empty.
The second test failed also for the same reason. I tried to follow some others questions with the official guide of Laravel Mocking. I know this is mocking object and not specially Eloquent.
Is something I'm not setting well?
If it's not possible to mock only the function, is possible to mock all the relationship of view, bypassing the DB access to that?
Edit
I undestand that the mocking work only when the class is injected from laravel, so what I wrote above it's not pratical.
I don't know if it's possible to mock only it, I saw a different option, that to create the interface of the model and change for the test, but I didn't want to make it.

Related

Laravel Models/function, how to make a main "function"

So guy's, I've created a Laravel project.
I have a master. Layout which always contains the user data.
So I have a navbar with $user->name for example.
In every controller I needed to add the User model and also the where function.
$user = User::find(auth()->user()->id)
Maybe this example is bad, but I've also included the company in the master, so it shows in the Navbar.
Is there a way, that I don't need to repeat that process? So I don't need it always in the controller.
Thanks for reading.
In laravel you are extending each class from a main controller so its better to create a method in main class like this
child controller
class testController extends Controller
{
// as you can see its extending so go into Controller class
}
parent class, So here i have creatd a getName method here. If you want get the value through mode
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
private $current_user_name = 'test';
public function getName()
{
return ($this->current_user_name);
}
}
Now go back to child controller and pass this method in view
class testController extends Controller
{
public function index()
{
return view('', $data = ['name' => $this->getName()]);
}
}
Hope this cover your query. In this way you don't need to repeat your code in every controller.
You can get data in your blade template too, like user information, but if you need more complex data and you don't want to put logic in blade, you can use this method (AppServiceProvider.php):
public function boot()
{
view()->composer('your_mast_layout', function($view)
{
$data = ...
$view->with('variable_name', $data);
});
}

Unit test Laravel query builder class, mock problem

I'm trying to mock out the actual database operations in my Laravel application for the purposes of effective unit testing. But I am having difficulty in getting the mocked method to be called, rather than the real method.
I have about 200 Models in app/Models which were generated using artisan make:model. I'm in the process of creating query builder classes which extend Illuminate\Database\Eloquent\Builder for each of the models. Likewise, there will be a PHPUnit test class for each of these query builders.
I've spent the last 2 days with the Laravel docs, the Mockery docs, Stack Overflow, Google and lots of debugging trying to get my unit test to work correctly, to no avail. What's the trick to making this work with Laravel's intricate internals?
Example Model, stripped down:
<?php
namespace App\Models;
use App\Domain\Example\QueryBuilders\ExampleQueryBuilder;
use Illuminate\Database\Eloquent\Model;
/**
* #property int $id
* #property string $foo
* #property string $bar
* #property string $baz
*/
class Example extends Model
{
public $timestamps = false;
protected $table = 'my_table';
protected $fillable = ['foo', 'bar', 'baz',];
protected $dateFormat = 'U';
protected $connection = 'mysql';
public function newEloquentBuilder($query): ExampleQueryBuilder {
return new ExampleQueryBuilder($query);
}
}
Example query builder, stripped down:
<?php
namespace App\Domain\Example\QueryBuilders;
use App\Models\Example;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
final class ExampleQueryBuilder extends Builder
{
protected $model;
protected $onDelete;
protected $query;
public function findActive(string $foo): ?Model {
$conditions = ['foo' => "$foo", 'bar' => 1,];
$example = Example::firstWhere($conditions);
if ($example instanceof Example) {return $example;}
return null;
}
Example unit test:
<?php
namespace Tests\Domain\Example\QueryBuilders;
use App\Models\Example;
use Tests\TestCase;
class ExampleQueryBuilderTest extends TestCase
{
/** #test */
public function it_handles_missing_example_correctly(): void {
// I've tried endless variations on this, mocking the Model "Example",
// mocking "Model", even "Process" at one crazy point. Debugging proves
// without a doubt that the `Builder::firstWhere` method IS being called
// instead of my mocked method.
$mock = $this->partialMock(Builder::class, function (MockInterface $mock) {
$mock->shouldReceive('firstWhere')->once();
});
$this->app->instance(Builder::class, $mock);
$example = Example::findActive('');
self::assertNull($example);
}
}
When this test is run, I get this output:
Method firstWhere() from Mockery_0_Illuminate_Database_Eloquent_Builder should be called
exactly 1 times but called 0 times.
So clearly the method I need to mock out is not getting mocked somehow. What am I overlooking or missing?
My environment: Laravel 8, Mockery 1.4, PHPUnit 9, PHP 7.4/8.0, macOS/Linux

Laravel : How to Pass an attribute to Eloquent model constructor

I want to use strtolower() before saving data in database for 5 attributes,
I'm using this code in Model
public function setFirstNameAttribute($value)
{
$this->attributes['firstName'] = strtolower($value);
}
public function setLastNameAttribute($value)
{
$this->attributes['lastName'] = strtolower($value);
}
public function setUserNameAttribute($value)
{
$this->attributes['userName'] = strtolower($value);
}
... etc
Can I use the __construct method instead of the above code?
There are two ways first one, to use boot method directly (preferred for small changes in model like in your question)
Method 1 :
we can directly use the boot method,
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Mymodel extends Model
{
public static function boot()
{
parent::boot();
static::saving(function ($model) {
// Remember that $model here is an instance of MyModel
$model->firstName = strtolower($model->firstName);
$model->lastName = strtolower($model->lastName);
$model->userName = strtolower($model->userName);
// ...... other attributes
});
}
}
Method 2 :
So we can use here a simple trait with a simple method for generating a strtolower() for a string.This is preferred when you have to do bigger changes in your model while performing operations in model like saving, creating etc. Or even if you want to use the same property in multiple models.
Create a trait MyStrtolower
<?php
namespace App\Traits;
trait MyStrtolower
{
public function mystrtolower($string)
{
return strtolower($string);
}
}
We can now attach this trait to any class that we want to have the mystrtolower method.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\Traits\MyStrtolower;
class Mymodel extends Model
{
use MyStrtolower; // Attach the MyStrtolower trait to the model
public static function boot()
{
parent::boot();
static::saving(function ($model) {
// Remember that $model here is an instance of MyModel
$model->firstName = $model->mystrtolower($model->firstName);
$model->lastName = $model->mystrtolower($model->lastName);
$model->userName = $model->mystrtolower($model->userName);
// ...... other attributes
});
}
}
If you want to not repeat all these lines of code for every model you make, make the trait configurable using abstract methods so that you can dynamically pass the attribute names for which you want to lower case string, like employee_name is Employee Model and user_name in User Model.

Laravel 5.8 add data to a layout variable via controller constructor

I am trying to add data to a layout variable via a controller constructor. The reason I want to do this is because I always need to add categories to the topmenu when this controller is called.
No success so far. I add data to a layout via a view composer like this.
namespace App\Http\ViewComposers;
use Illuminate\View\View;
use App\Menu;
class MenuComposer
{
public function compose(View $view)
{
if (in_array($view->getName(), ['layouts.master', 'layouts.master-post', 'layouts.error']))
{
$menu = Menu::menu('topmenu');
view()->with('topmenu', $menu);
// view()->share('topmenu', $menu); not working either
}
}
}
I want to extend the data in a Controller constructor.
namespace App\Http\Controllers\Post;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PostController extends Controller {
public function __construct(View $view)
{
$view->offsetGet('topmenu');
// $view->gatherData() not working either
}
Whatever I try, Laravel throws an exception:
Target [Illuminate\Contracts\View\Engine] is not instantiable while building [App\Http\Controllers\Post\PostController, Illuminate\View\View].
What I did in the serviceprovider boot function:
view()->share('topmenu', [
'items' => $newItemsToAdd
]);
In the viewComposer I did:
$extraItems = view()->shared('topmenu');
if (!empty($extraItems)) {
$items = aray_merge($items, $extraItems);
}
}

Laravel: Class not found if it is called from a Trait

After creating several Apps with Laravel and using softDelete properties I realized that methods like destroy(), restore() and kill() are exactly the same among several controllers. Therefore I am trying to put themn in a trait and use it from diferent Controllers.
My code is as follows:
ProfilesController.php
<?php
namespace App\Http\Controllers;
use App\Profile;
class ProfilesController extends Controller
{
public function destroy(Profile $profile)
{
Profile::del($profile, 'profiles');
return redirect()->route('profiles.index');
}
public function trashed()
{
Profile::trash('Profile');
}
}
Profile.php (model)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Profile extends Model
{
protected $fillable = ['user_id', 'role_id', 'title', 'subtitle', 'slug', 'birthday', 'about'];
use SoftDeletes, Helpers, commonMethods;
public function getRouteKeyName()
{
return 'slug';
}
// ... more code here
}
trait file: commonMethods.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use App\Profile;
use Session;
trait commonMethods
{
public static function del($element, $page_name)
{
$element->delete();
Session::flash('success', $element . ' successfully deleted!');
}
public static function trash($model)
{
$total = $model::onlyTrashed()->get();
$total_tr = count($total);
$all_tr = $model::all();
return view('partials.templates.trashed', compact('total', 'total_tr', 'all_tr'));
}
// ...more code here
}
The problem:
I try to visit the view "Trashed" that will list all elements "softdeleted" but not "killed", the method.
I pass the $model variable with the method trash($model)
I get the following error:
Class App/Profile does not found. Try to call App/Profile
I have debugged and the $model variable contains exactly what I need, the string 'Profile' which is what I need to build the Query:
$total = Profile::onlyTrashed()->get();
This query works while in the ProfilesController, but does not work while in a trait, since the model class is not found.
Any idea how could I make it work?
I am using Laravel 6.
If you need to use a class as a string you will want to use its full name. 'App\Profile' instead of 'Profile'.
$model = 'Profile';
new $model; // will use `\Profile`
$model = 'App\Profile';
new $model; // will use '\App\Profile';
In your controller( ProfilesController ) write :
use App\Profile;
In your model write :
use App\commonMethods;

Resources