unit test for relationship in laravel Model - php

I am new for laravel model unit testing.so please look and suggest me what i am doing wrong.my code is given below.I have 2 models User and UserState.
Model User
public function state()
{
return $this->hasOne('UserState');
}
Model UserState
public function user()
{
return $this->belongsTo('User');
}
now i am writing unit test for UserState. which is given below :
UnitTest UserStateModelTest
public function testUserRelationIsTrue(){
$user = new User();
$user->username = 'testusername';
$user->save();
$this->assertEquals($user->user_id, $user->state->id);
}
during test run by phpunit it generate error
Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation:
1452 Cannot add or update a child row: a foreign key constraint fails

If you really want to test a relationship method, you can do it without even saving a model to the database.
You still need to use the RefreshDatabase trait (or the DatabaseMigrations trait) or else the models won't be mapped to any table.
# tests/Unit/ParentTest.php
/**
* Test Parent has HasMany relationship with Child model
* #test
*/
public function has_many_children_with_parent_id_fk()
{
$parent = new Parent;
$foreign_key = 'parent_id';
// Get the relationship object, not the data collection
$relationship = $parent->children();
$related_model = $relationship->getRelated();
// Assert this is a HasMany relationship
$this->assertInstanceOf(HasMany::class, $relationship);
// Assert the related model is Child
$this->assertInstanceOf(Child::class, $related_model);
// Assert the foreign key is the one we specified
// (This can be useful if you do not use the default one)
$this->assertEquals($foreign_key, $relationship->getForeignKeyName());
// Assert the foreign key is a column
// of the database table mapped by the Child model
$this->assertTrue(Schema::hasColumns($related_model->getTable(), array($foreign_key)));
}
# tests/Unit/ChildTest.php
/**
* Test Child has belongsTo relationship with Parent model
* #test
*/
public function belongs_to_parent_with_parent_id_fk()
{
$child = new Child;
$foreign_key = 'parent_id';
// Get the relationship object, not the data collection
$relationship = $child->parent();
$related_model = $relationship->getRelated();
// Assert this is a BelongsTo relationship
$this->assertInstanceOf(BelongsTo::class, $relationship);
// Assert the related model is Parent
$this->assertInstanceOf(Parent::class, $related_model);
// Assert the foreign key is the one we specified
// (This can be useful if you do not use the default one)
$this->assertEquals($foreign_key, $relationship->getForeignKeyName());
// Assert the foreign key is a column
// of the database table mapped by the Child model
$this->assertTrue(Schema::hasColumns($relationship->getParent()->getTable(), array($foreign_key)));
}
This is a bit of a handful but you can make a custom assertion to encapsulate all of this in the TestCase all tests files extend. The following methods suited my needs
# tests/TestCase.php
public function assertHasManyUsing($related_model, $relationship, $foreign_key)
{
$this->assertInstanceOf(HasMany::class, $relationship);
$this->assertInstanceOf($related_model, $relationship->getRelated());
$this->assertEquals($foreign_key, $relationship->getForeignKeyName());
$this->assertTrue(Schema::hasColumns($relationship->getRelated()->getTable(), array($foreign_key)));
}
public function assertBelongsToUsing($related_model, $relationship, $foreign_key)
{
$this->assertInstanceOf(BelongsTo::class, $relationship);
$this->assertInstanceOf($related_model, $relationship->getRelated());
$this->assertEquals($foreign_key, $relationship->getForeignKeyName());
$this->assertTrue(Schema::hasColumns($relationship->getParent()->getTable(), array($foreign_key)));
}
And now, refactoring the tests look like this
# tests/Unit/ParentTest.php
/**
* Test Parent has HasMany relationship with Child model
* #test
*/
public function has_many_children_with_parent_id_fk()
{
$parent = new Parent;
$this->assertHasManyUsing(Child::class, $parent->children(), 'parent_id');
}
# tests/Unit/ChildTest.php
/**
* Test Child has belongsTo relationship with Parent model
* #test
*/
public function belongs_to_parent_with_parent_id_fk()
{
$child = new Child;
$this->assertBelongsToUsing(Parent::class, $child->parent(), 'parent_id');
}

Argument #1 of PHPUnit\Framework\Assert::assertInstanceOf() must be a
class or interface name
tests\TestCase.php:14
> 10▕ use CreatesApplication;
> 11▕
> 12▕ public function assertHasManyUsing($related_model, $relationship, $foreign_key)
> 13▕ {
> ➜ 14▕ $this->assertInstanceOf(HasMany::class, $relationship);
> 15▕ $this->assertInstanceOf($related_model, $relationship->getRelated());
> 16▕ $this->assertEquals($foreign_key, $relationship->getForeignKeyName());
> 17▕ $this->assertTrue(Schema::hasColumns($relationship->getRelated()->getTable(),
> array($foreign_key)));
> 18▕ }

I am testing model relationships in this way
$this->assertTrue($comment->user()->exists());

Related

Is there a way with Doctrine for the parent and the children to have the same parent constraint?

I use Doctrine as the ORM with PostgreSQL. I have many entities, but here is my constraint :
A series has many seasons
A season has many episodes
I had a relation between episodes and series, to have a function series->getEpisodes() (without passing through season).
My question : Is there a way to tell Doctrine that episode and season must have the same series ? In another words, to add a constraint that say a child and his parent must have a common parent ?
Because without this constraint, it is theoretically possible for the episode (child) and the season (parent) to have a different series (but I avoid this with the application right now).
You have to create your own validation constraint. https://symfony.com/doc/current/validation/custom_constraint.html
/**
* #Annotation
*/
class HasSameSeason extends Constraint
{
public $message = 'The episode\'s serie must match the season serie';
public function validatedBy()
{
return static::class.'Validator';
}
}
class HasSameSeasonValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof HasSameSeason) {
throw new UnexpectedTypeException($constraint, HasSameSeason::class);
}
if (!$value instanceof Episode) {
throw new UnexpectedTypeException($value, Episode::class);
}
if ($episode->getSerie() !== $episode->getSeason()->getSerie()) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}

Symfony Doctrine relationship empty in PhpUnit test

I have a Symfony app with a User entity with a many-to-many relation to a Cat entity. I also have a PhpUnit test which checks that deleting a cat (that belongs to 2 users) from 1 user doesn't actually delete the cat:
public function testDeletingACatBelongingToTwoUsersOnlyDeletesTheAssociationNotTheCat()
{
$cat = $this->createCat();
// Associate with user 1
$user1 = new User();
$user1->setEmail('test#example.com');
$user1->setPassword('pwdpwd');
$user1->addCat($cat);
$this->em->persist($user1);
// Associate with user 2
$user2 = new User();
$user2->setEmail('another#example.com');
$user2->setPassword('pwdpwd');
$user2->addCat($cat);
$this->em->persist($user2);
$this->em->flush();
// Sanity check:
$this->assertCount(1, $user1->getCats()); // PASS
$this->assertCount(1, $user2->getCats()); // PASS
$this->assertCount(2, $cat->getUsers()); // FAIL (0)
// ... perform the test (not shown here)
}
private function createCat(): Cat
{
$cat = new Cat();
$cat->setName($this->name);
$this->em->persist($cat);
$this->em->flush();
return $cat;
}
My question is, why does $cat->getUsers() return 0 in my test? At runtime it doesn't, it returns the correct value. It's only in the test that it returns 0.
Here are the relevant excerpts from my entities, auto-generated by Symfony:
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* #ORM\ManyToMany(targetEntity=Cat::class, inversedBy="users")
*/
private $cats;
/**
* #return Collection|Cat[]
*/
public function getCats(): Collection
{
return $this->cats;
}
public function addCat(Cat $cat): self
{
if (!$this->cats->contains($cat)) {
$this->cats[] = $cat;
}
return $this;
}
public function removeCat(Cat $cat): self
{
$this->cats->removeElement($cat);
return $this;
}
}
/**
* #ORM\Entity(repositoryClass=CatRepository::class)
*/
class Cat
{
/**
* #ORM\ManyToMany(targetEntity=User::class, mappedBy="cats")
*/
private $users;
/**
* #return Collection|User[]
*/
public function getUsers(): Collection
{
return $this->users;
}
}
The reason is, that collections are not synchronized with the database and synchronization between owning and inverse side is not automatically done either.
The category entries of your user entity probably will be persisted to the database (although I'm missing some cascade statements, but what do I know). When the category is created, it's collection of users is empty (obviously), then users add the category to the many-to-many relation in database.
BUT, the collection is a plain collection. If you loaded the category from the database, it would be a lazy-loaded PersistentCollection (or something alike), which would - only at the moment of access - fetch the items from the database (definition of lazy loading). Your test code has the plain collection (since you created the object yourself).
Not quite sure, if it'll work, but you could try refreshing the cat ($em->refresh($cat);) I'm not quite certain though, if that will replace the collection. Alternatively, you could make your User::addCat that it also calls $cat->addUser($this) (which you might have to add, beware the infinite recursion, which already should be prevented by the "contains" checks.).

Wrapping eloquent model to another class causes exceeded nesting

I'm using Laravel 5.5. I wrote a wrapper that takes an Eloquent model and wraps it to an Entity class and each model has own wrapper. Assume, the User has many products and a Product belongs to one user. When wrapping, I need to get products of a user and pass them to product wrapper to wrap them into the product entities. In the product wrapper, I need to get user owner of this product to wrap it to the user entity. So again, In the user wrapper, I need user products!, and this creates an infinite loop.
EntityWrapper:
abstract class EntityWrapper
{
protected $collection;
protected $entityClass;
public $entity;
public function __construct($collection)
{
$this->collection = $collection;
$this->entity = $this->buildEntity();
}
protected function buildEntity()
{
$tempEntity = new $this->entityClass;
$Entities = collect([]);
foreach ($this->collection as $model) {
$Entities->push($this->makeEntity($tempEntity, $model));
}
return $Entities;
}
abstract protected function makeEntity($entity, $model);
}
UserEntityWrapper:
class UserEntityWrapper extends EntityWrapper
{
protected $entityClass = UserEntity::class;
protected function makeEntity($userEntity, $model)
{
$userEntity->setId($model->user_id);
$userEntity->setName($model->name);
// set other properties of user entity...
//--------------- relations -----------------
$userEntity->setProducts((new ProductEntityWrapper($model->products))->entity);
return $userEntity;
}
}
ProductEntityWrapper:
class ProductEntityWrapper extends EntityWrapper
{
protected $entityClass = ProductEntity::class;
protected function makeEntity($productEntity, $model)
{
$productEntity->setId($model->product_id);
$productEntity->setName($model->name);
// set other properties of product entity...
//--------------- relations -----------------
$productEntity->setUser((new UserEntityWrapper($model->user))->entity);
return $productEntity;
}
}
UserEntity:
class UserEntity
{
private $id;
private $name;
private $products;
//... other properties
public function setProducts($products)
{
$this->products = $products;
}
// other getters and setters...
}
When I wnat to get user entities by calling (new UserEntityWrapper(User::all()))->entity, It causes infinite loop. So, how can I prevent the nesting call to relationship between models? Thanks to any suggestion.
Finally I found the solution. As in each wrapper class, I used the dynamic property to get the relationship collection, in addition to imposing extra queries, this causes lazy loading. So, before passing the model collection into each wrapper, the necessary relationship model is retrieved and each wrapper firstly checks the existence of relationship using method getRelations() (that returns an array of available relations). If intended relationship is available, the collection of relationship models is passed into the proper wrapper class.
UserEntityWrapper:
class UserEntityWrapper extends EntityWrapper
{
protected $entityClass = UserEntity::class;
protected function makeEntity($userEntity, $model)
{
$userEntity->setId($model->user_id);
$userEntity->setName($model->name);
// set other properties of user entity...
//--------------- relations -----------------
$relations = $model->getRelations();
$products = $relations['products'] ?? null;
if ($products) {
$userEntity->setProducts((new ProductEntityWrapper($products))->entity);
}
return $userEntity;
}
}
And, a similar functionality is used for the other wrappers.

How to write a relationship to a distant model through a polymorphic relation

I have a polymorphic relation where class (Request) can have a relationship with either class (Leave) or class (Overtime).
Each object of class (Request) belongs to a user.
I would like to setup a relationship in the User class to directly get all of their Leave or Overtime objects.
Here is how the code looks:
Class Request with:
user_id
requestable_id
requestable_type can be App\Leave or App\Overtime
class Request extends Model
{
public function requestable() {
return $this->morphTo();
}
public function user() {
return $this->belongsTo('App\User');
}
}
Class Leave
class Leave extends Model
{
public function request() {
return $this->morphOne('App\Request', 'requestable');
}
}
Class Overtime
class Overtime extends Model
{
public function request() {
return $this->morphOne('App\Request', 'requestable');
}
}
Class User
class User extends Authenticatable
{
public function requests() {
return $this->hasMany(Request::class);
}
public function leaves() {
// Need help here
}
public function overtimes() {
// And here
}
}
What I would like to do is get all leaves and overtimes a user has, so ultimately I should be able to do this:
$userLeaves = $user->leaves;
$userOvertimes = $user->overtimes;
Seems that you need a combination of polymorphic relation (that you've already defined) and hasManyThrough.
return $this->hasManyThrough(Leave::class, Request::class);
and
return $this->hasManyThrough(Overtime::class, Request::class);
respectively. But check foreign and local keys (see more info here).
you can fetch the user leaves and overtimes through the user requests by using
$requests = $user->requests->with('requestable');
but this will fetch all user requests not depending on the type, however you can fetch them depending on the type by using the leaves and overtimes function if you want and specifying the type there
User Class
public function leaves()
{
return $this->requests->where('requestable_type', 'App\Leave');
}
public function overTimes()
{
return $this->requests->where('requestable_type', 'App\OverTime');
}
Answering my own question.
Using hasManyThrough:
public function leaves() {
return $this->hasManyThrough(
Leave::class, // the class that we want objects from
Request::class, // the class sitting between this one and the target
'user_id', // this class's foreign key in the request class
'id', // foreign key in leave class
'id', // local key in this class
'requestable_id' // key of the leave in the request class
)
// we have to limit it to only Leave class
->where('requestable_type', array_search(Leave::class, Relation::morphMap()) ?: Leave::class);
}
public function overtimes() {
return $this->hasManyThrough(
Overtime::class, // the class that we want objects from
Request::class, // the class sitting between this one and the target
'user_id', // this class's foreign key in the request class
'id', // foreign key in overtime class
'id', // local key in this class
'requestable_id' // key of the overtime in the request class
)
// we have to limit it to only overtime class
->where('requestable_type', array_search(Overtime::class, Relation::morphMap()) ?: Overtime::class);
}

laravel 5.1 BelongsTo Reverse call

What i know:
$this->$parent->childs(); //we get childs data
what i want to know how:
$this->child->find($id)->parent(); //how to get childs parent without including model in controller | by just using eloquent
heres my sample code of employee and employeeDependent model:
trait EmployeeRelationships{
public function dependents(){
return $this->hasMany(\App\DB\EmployeeDependent\EmployeeDependent::class);
}
}
trait EmployeeDependentRelationships{
/**
* #return mixed
*/
public function employee(){
return $this->belongsTo(\App\DB\Employee\Employee::class, 'employee_id');
}
}
If you want to get the reverse of a BelongsTo relationship you need to specify the inverse of the relationship on the corresponding model. For example:
Employee Class
class Employee extends Model
{
public dependents()
{
return $this->hasMany(Dependant::class);
}
}
Dependent Class
class Dependent extends Model
{
public employee()
{
return $this->belongsTo(Employee::class, 'employee_id');
}
}
With these relationships defined you can then access the relevant models by calling the appropriate methods like so:
$dependents = Employee::first()->dependents; // Returns an eloquent collection
$employee = Dependent::first()->employee; // Returns a model of type Employee
Note that in this example using the first() method to grab a model, you can can do this with any object of the correct type.

Categories