PhpUnit - mocking laravel model with relations - laravel

I'm trying to mock (it's example only) $user->posts()->get().
example service:
use App\Models\User;
class SomeClass{
public function getActivePost(User $user): Collection
{
return $user->posts()->get();
}
}
and my Model:
and Model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use \App\Models\Post;
class User extends Model
{
public function posts() : HasMany
{
return $this->hasMany(Post::class);
}
}
this doesn't work:
$this->user = Mockery::mock(User::class);
$this->user
->shouldReceive('wallets->get')
->andReturn('test output');
error:
TypeError: Return value of Mockery_2_App_Models_User::posts() must be an instance of Illuminate\Database\Eloquent\Relations\HasMany, instance of Mockery_4__demeter_posts returned
without return type hint (on post() method) everything is ok. Must I modify andReturn()? idk how

This error can be solved by using the alias prefix with a valid class name. Like the following:
$m = m::mock('alias:App\Models\User');
More information can be found at the official documentation http://docs.mockery.io/en/latest/reference/creating_test_doubles.html#aliasing

Alternatively you can use like this.
use App\Models\User;
class SomeClass{
public function getActivePost(User $user): Collection
{
$user->load('posts');
return $user->posts;
}
}
First you need to mock post, then add it to Collection (don't forget to use it in the top). Then when you call posts attribute its takes mocked $posts. In this case it will not throw error about return type.
use Illuminate\Database\Eloquent\Collection;
$post = $this->mock(Post::class)->makePartial();
$posts = new Collection([$post]);
$this->user = Mockery::mock(User::class);
$this->user
->shouldReceive('getAttribute')
->with('posts');
->andReturn($posts);

Also i wouldn't use mocks here. There is absolutely no need for it. So the unit test i write would be:
Create a user.
Create some posts authored by the user.
Perform assertions on user & posts.
So the code will then be something like this in my test:
$user = factory(User::class)->create();
$posts = factory(Post::class, 5)->create(['user_id' => $user->id]);
$this->assertNotEmpty($user->id);
$this->assertNotEmpty($posts);
$this->assertEquals(5, $posts->fresh()->count());
$this->assertEquals($user->id, $post->fresh()->first()->user_id);

if you want to test the relationship you can:
/** #test */
function user_has_many_posts()
{
$user = factory(User::class)->create();
$post= factory(Post::class)->create(['user_id' => $user->id]);
//Check if database has the post..
$this->assertDatabaseHas('posts', [
'id' => $post->id,
'user_id' => $user->id,
]);
//Check if relationship returns collection..
$this->assertInstanceOf('\Illuminate\Database\Eloquent\Collection', $user->posts);
}

Related

Laravel: How to assign Organization to a user

I want to assign an organization to a user but what happens in my code is that when I create a new organization and it's ID is 1, it automatically assigns itself to user ID 1 also.
This is my AssignOrgToUser controller:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\organizations;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class AssignOrgToUserController extends Controller
{
public function assignOrg(Request $request, $id)
{
$users = User::find($id);
if(is_null($users)){
return response()->json(["message"=>"User not found!"], 404);
}
$rules=[
'organization'=>'required',
];
$validator = Validator::make($request->all(), $rules);
if($validator->fails()){
return response()->json($validator->errors(),400);
}
$data = $request->validate([
'organization'=>'required',
]);
$orgs = organizations::where('id', '=', $request->organization)->first();
if(is_null($orgs)){
return response()->json(["message"=>"Organization not found!"], 404);
}
$orgs= $users->save();
if($orgs){
return ["result"=>"ORG Added"];
}else{
return ["result"=>"ORG not Added"];
}
// $users->save([$orgs]);
// return response(['message'=>"Organization has beed added", $users]);
}
}
Organization Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class organizations extends Model
{
public $table = "organizations";
use HasFactory;
protected $guarded = [];
public function users(){
return $this->belongsTo('App\Models\User'); #if column not found indicate the column name
}
}
Any kind of help/suggestions will be greatly appreciated. Thank you!!
Replace this $orgs= $users->save(); with the below code
For update use associate method do like this (in your Case)
$orgs = Organisation::create(['someColumn' => $request->someColumn]);
$orgs->users()->associate($users);
$orgs->save();
for more https://laravel.com/docs/8.x/eloquent-relationships#inserting-and-updating-related-models
It seams like the problem came from you Request URL, you have define you Controller to receive a paramater name id with that I can presume you have define the Route like this
Route::post("/assign_organization/{id}", [AssignOrgToUserController::class, "assignOrg"]);
If the request url contain a user ID which is 1 any time you'll try to create an organization it will be attached to a user which ID is 1. as you are retrieving the user based on the id get from the URL
$user = User::find($id);
If you want to assign an organization with a different User ID, you should pass that User ID in you request body. and that won't consider the URL Body
$user = User::find($request->get("user_id"));

Laravel error using with() method in Eloquent

I'm trying to call using with() method in Laravel Eloquent ORM, but getting the following error.
Argument 1 passed to
App\Http\Controllers\DashboardController::App\Http\Controllers\{closure}()
must be an instance of Illuminate\Database\Eloquent\Builder,
instance of Illuminate\Database\Eloquent\Relations\HasMany given
I'm using the latest version of Laravel 6. Any ideas what might have caused this?
Controller
class DashboardController extends Controller
{
public function __construct()
{
$this->middleware('auth:api');
}
public function formIndex(Request $request)
{
$id = auth()->user()->id;
$item = Groupe::find($id)->with(
[
'etudiants' => function (Builder $query) {
$query->select('id');
}
]
)->first();
return $item;
}
}
Model
class Groupe extends Authenticatable implements JWTSubject
{
public function etudiants()
{
return $this->hasMany('App\Etudiant');
}
}
The error comes from the type hint you put on the $query variable, as the error message said the object that gets passed in there is a relationship not a raw Query Builder. Just remove the type hint. Also ::find() executes a query, so you're be executing 2 queries, use the query below instead
Groupe::where('id', $id)->with(['etudiants' => function ($query) {
$query->select('id');
}])->first();
Additionally, you don't need to use the callback syntax to only eager load certain columns, the callback syntax is for putting contraints on which records get returned. Try this instead.
Groupe::where('id', $id)->with('etudiants:id,name,email')->first();
But what do you want return? groupe->students[]?? you can use
$item = Groupe::where('id',$id)->with('etudiants')->first();

Where is the create method of BelongsTo class?

I have a general class for uploads.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class File extends Model
{
protected $guarded = ['id'];
}
An ID of File model will be specified on each person to serve as the avatar:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Person extends Model
{
public function avatar()
{
return $this->belongsTo('App\File');
}
public function putAvatar($file)
{
$path = $file->store('avatars');
// This would work if `avatar()` was a `hasMany()` relation
$this->avatar()->create([
'path' => $path,
]);
}
}
This doesn't work exactly as intended, but it creates the File model in database. Why?
The $this->avatar() is an instance of BelongsTo and there is no create method. I checked the class, the included traits and the Relation class that it extends. Reference here.
So what's going on, where is the code that creates the new model?
I tried using a ReflectionMethod but while $this->avatar()->create() works, new ReflectionMethod($this->avatar(), 'create') returns a ReflectionException with message Method Illuminate\Database\Eloquent\Relations\BelongsTo::create() does not exist.
There is no method for saving entities on belongsTo relationships. Once the entity is created, you can associate it with the model.
$avatar = File::create([...]);
$this->avatar()->associate($avatar)->save();
To allow querying of relationships, undefined method calls are passed to an Eloquent Builder instance which does have a create method.
All relationships extend the Relation class which defines:
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
$result = $this->query->{$method}(...$parameters);
if ($result === $this->query) {
return $this;
}
return $result;
}
The method used for belongsTo() should be save(), not created(). Make sure to pass as an argument your File class:
$this->avatar()->save(new File([
'path' => $path,
]));

Where provide "morphMap" setting for standalone eloquent usage without laravel

I am using eloquent as standalone module with slim framework (https://www.slimframework.com/docs/cookbook/database-eloquent.html)
My question is - where i can provide polymorphic relations "morphMap" setting?
you should define the morphMap in each function that define in morph or morphToMany:
example::
here is the comments function model:
class comment extends Model
{
public function commentable(){
Relation::morphMap([
'posts' => 'App\post',
'videos' => 'App\video',
]);
return $this->morphTo();
}
}
here is the post function model:
class post extends Model
{
public function comments(){
Relation::morphMap([
'posts' => 'App\post',
]);
return $this->morphMany('App\comment','commentable');
}
}
dont forget to add this namespace:
use Illuminate\Database\Eloquent\Relations\Relation;
To avoid to use morphMap in each model's relation you can override boot method setting there your morphMap:
class Post extends Model
{
protected static function boot(){`
parent::boot();
Relation::morphMap([
'posts' => 'App\post',
]);
}
public function comments(){
return $this->morphMany('App\Comment', commentable);
}
}
Today i searched for the same problem.
Slim4 & Eloquent.
I have to following code in my container boot file and it works.
container.php
// eloquent
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($dbSettings);
$capsule->setAsGlobal();
$capsule->bootEloquent();
$container->set('db', function ($container) use ($capsule) {
Illuminate\Database\Eloquent\Relations\Relation::morphMap([
App\Models\Order::class
]);
return $capsule;
});
now my database contains order instead of App\Models\Order
Don`t know if this is the correct way.
But this way I only have to write it in one place.

Laravel Mutators with Constructor?

Ill have a problem because my mutators never get called when ill use an constructor:
Like this:
function __construct() {
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // Never gets called
}
Ill already found out, that the mutators would ne be called when ill use an constructor, so i should use:
public function __construct(array $attributes = array()){
parent::__construct($attributes);
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // now its getting called
}
But so ill get the following error:
array_key_exists() expects parameter 2 to be array, null given
But i dont know where? Can anyone help me out how to create a default value (like a UUID) for a specific column, and use mutators in the same class?
Edit: Thanks Martin Bean for your help, but i am now getting the following error:
Cannot declare class App\Uuid because the name is already in use
I have tried:
Creating a File called "Uuid.php" in /app/ -> /app/Uuid.php
With this content:
<?php namespace App;
use Webpatser\Uuid\Uuid;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = Uuid::generate(4)->string();
});
}
}
Changed my Model to:
<?php namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Task extends Model {
use \App\Uuid;
Thank you very much!
Edit 2:
Ill tried it this way:
class Task extends Model {
protected $table = 'tasks';
protected $fillable = ['..... 'date', 'guid'];
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
TaskController:
public function store() {
$input = Request::all();
$input['guid'] = true;
Task::create($input);
return redirect('/');
}
Works fine, but when ill use:
public function setDateAttribute(){
$this->attributes['date'] = date('Y-m-d', $date);
}
In Task.php ill get:
Undefined variable: date
EDITED:
based on your comment:
i would like to set a field on first insert
use Uuid; //please reference the correct namespace to Uuid
class User extends Model{
protected $fillable = [
'first_name',
'email',
'guid' //add guid to list of your fillables
]
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
}
Later:
$user = User::create([
'guid' => true, //setAttribute will handle this
'first_name' => 'Digitlimit',
'email" => my#email.com
]);
dd($user->guid);
NB: Remove the __construct() method from your model
Mutators are called when you try and set a property on the model—they’re invoked via the __get magic method. If you manually assign a property in a method or constructor, then no mutators will ever be called.
Regardless, you should not be creating constructors on Eloquent model classes. This could interfere with how Eloquent models are “booted”.
If you need to set an UUID on a model then I’d suggest using a trait that has its own boot method:
namespace App;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = \Vendor\Uuid::generate(4)->string();
});
}
}
You apply the trait to your model…
class SomeModel extends Model
{
use \App\Uuid;
}
…and now each time a model is created, a UUID will be generated and stored in the database with your model.

Resources