Part 3: Testing Model Relationships in Laravel — POLYMORPHIC

This part deals with more complex model relationships, the polymorphic type. Just like before, we shall stick to the official documentation examples and work out befitting tests. To have a low down on the earlier parts see link directly below 👇
- Part 1: Intro and Schema Tests
- Part 2: Testing Basic Model Relationships
- Part 3: Testing Polymorphic Relationships ⏯
POLYMORPHIC RELATIONSHIPS
A polymorphic relationship allows a target model to belong to more than one type of model using a single association. — Laravel.com
1. One-to-One Polymorphic Relationship
A one-to-one polymorphic relation is similar to a simple one-to-one relation; however, the target model can belong to more than one type of model on a single association.
Scenario:
A blog Post and a User may share a polymorphic relation to an Image model. Using a one-to-one polymorphic relation allows you to have a single list of unique images that are used for both blog post s and user accounts
morphTo()
— An image table can be morph ed in to any model (i.e serve many different models), eg User or Post model in our case.
// App/Image.php
...
public function imageable()
{
return $this->morphTo();
}
<?php | |
namespace Tests\Unit; | |
use App\Country; | |
use App\Image; | |
use App\Post; | |
use App\Supplier; | |
use App\User; | |
use Illuminate\Foundation\Testing\RefreshDatabase; | |
use Illuminate\Foundation\Testing\WithFaker; | |
use Illuminate\Support\Facades\Schema; | |
use Tests\TestCase; | |
class ImagesTest extends TestCase | |
{ | |
use RefreshDatabase, WithFaker; | |
public function setUp() :void | |
{ | |
parent::setUp(); | |
$this->country = factory(Country::class)->create(); | |
$this->supplier = factory(Supplier::class)->create(); | |
$this->user = factory(User::class)->create(); | |
$this->post = factory(Post::class)->create(); | |
$this->image = factory(Image::class)->create(); | |
} | |
/** @test */ | |
public function images_database_has_expected_columns() | |
{ | |
$this->assertTrue(Schema::hasColumns('images', [ | |
'id', "url", "imageable_id", "imageable_type" | |
]), 1); | |
} | |
/** @test */ | |
public function an_image_can_be_uploaded_by_a_user() // morphedTo a USER | |
{ | |
$image = factory(Image::class)->create([ | |
"imageable_id" => $this->user->id, | |
"imageable_type" => "App\User", | |
]); | |
$this->assertInstanceOf(User::class, $image->imageable); | |
} | |
/** @test */ | |
public function an_image_can_be_uploaded_for_a_post() // morphedTo a POST | |
{ | |
$image = factory(Image::class)->create([ | |
"imageable_id" => $this->post->id, | |
"imageable_type" => "App\Post", | |
]); | |
$this->assertInstanceOf(Post::class, $image->imageable); | |
} | |
} |
morphOne()
- — A user/post model can morph one instance of the same image model table (i.e make use of same image table but one record per user/post).
- — INVERSE of morphTo relationship.
// App/User.php
...
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
<?php | |
namespace Tests\Unit; | |
use App\Country; | |
use App\Image; | |
use App\Post; | |
use App\Supplier; | |
use App\User; | |
use Illuminate\Foundation\Testing\RefreshDatabase; | |
use Illuminate\Foundation\Testing\WithFaker; | |
use Illuminate\Support\Facades\Schema; | |
use Tests\TestCase; | |
class UserTest extends TestCase | |
{ | |
use RefreshDatabase, WithFaker; | |
public function setUp() :void | |
{ | |
parent::setUp(); | |
$this->country = factory(Country::class)->create(); | |
$this->supplier = factory(Supplier::class)->create(); | |
$this->user = factory(User::class)->create(); | |
$this->post = factory(Post::class)->create(); | |
$this->image = factory(Image::class)->create([ | |
"imageable_id" => $this->user->id, | |
"imageable_type" => "App\User", | |
]); | |
} | |
// ... | |
/** @test */ | |
public function a_user_morphs_one_image() | |
{ | |
$this->assertInstanceOf(Image::class, $this->user->image); | |
} | |
} |
Files relevant to this test 👇
- **MODEL FILES(App/)
-- User.php
-- Post.php
-- Image.php
- **MIGRATION FILES(database/migrations/)
-- 2014\10\12\000000\create\users\table.php
-- 2019\09\27\100604\create\posts\table.php
-- 2019\09\28\143143\create\images\table.php
- **MODEL FACTORY FILES(database/factories/)
-- UserFactory.php
-- PostFactory.php
-- ImageFactory.php
- **UNIT TEST FILES(tests/Units/)
-- UserTest.php
-- PostsTest.php
-- ImagesTest.php
2. One-to-Many (Polymorphic)
A “one-to-many polymorphic” relation is similar to a simple one-to-many relation; however, the target model can belong to more than one type of model on a single association. — Laravel.com
Scenario:
Users of your application can “comment” on both posts and videos. Using polymorphic relationships, you may use a single comments table for both of these scenarios.
morphTo()
— A comment table can be morph ed to any model (i.e serve many different models), eg Video or Post model in our case.
// App/Comment.php
...
public function commentable()
{
return $this->morphTo();
}
<?php | |
namespace Tests\Unit; | |
use App\Comment; | |
use App\Country; | |
use App\Post; | |
use App\Supplier; | |
use App\User; | |
use App\Video; | |
use Illuminate\Foundation\Testing\RefreshDatabase; | |
use Illuminate\Foundation\Testing\WithFaker; | |
use Illuminate\Support\Facades\Schema; | |
use Tests\TestCase; | |
class CommentsTest extends TestCase | |
{ | |
use RefreshDatabase, WithFaker; | |
public function setUp() :void | |
{ | |
parent::setUp(); | |
$this->country = factory(Country::class)->create(); | |
$this->supplier = factory(Supplier::class)->create(); | |
$this->user = factory(User::class)->create(); | |
$this->post = factory(Post::class)->create(); | |
$this->video = factory(Video::class)->create(); | |
$this->comment = factory(Comment::class)->create(); | |
} | |
// ... | |
/** @test */ | |
public function a_comment_can_be_morphed_to_a_video_model() | |
{ | |
$comment = factory(Comment::class)->create([ | |
"commentable_id" => $this->video->id, | |
"commentable_type" => "App\Video", | |
]); | |
$this->assertInstanceOf(Video::class, $comment->commentable); | |
} | |
/** @test */ | |
public function a_comment_can_be_morphed_to_a_post_model() | |
{ | |
$comment = factory(Comment::class)->create([ | |
"commentable_id" => $this->post->id, | |
"commentable_type" => "App\Post", | |
]); | |
$this->assertInstanceOf(Post::class, $comment->commentable); | |
} | |
} |
morphMany()
- — A video/post model can morph many instances of the same comment model table (i.e make use of same comment table for many records).
- — INVERSE of morphTo relationship.
// App/Video.php
...
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
<?php | |
namespace Tests\Unit; | |
use App\Comment; | |
use App\Country; | |
use App\Post; | |
use App\Supplier; | |
use App\User; | |
use App\Video; | |
use Tests\TestCase; | |
use Illuminate\Foundation\Testing\WithFaker; | |
use Illuminate\Foundation\Testing\RefreshDatabase; | |
use Illuminate\Support\Facades\Schema; | |
class VideosTest extends TestCase | |
{ | |
use RefreshDatabase, WithFaker; | |
public function setUp() :void | |
{ | |
parent::setUp(); | |
$this->country = factory(Country::class)->create(); | |
$this->supplier = factory(Supplier::class)->create(); | |
$this->user = factory(User::class)->create(); | |
$this->post = factory(Post::class)->create(['user_id' => $this->user->id]); | |
$this->video = factory(Video::class)->create(['user_id' => $this->user->id]); | |
$this->comment = factory(Comment::class)->create([ | |
"commentable_id" => $this->video->id, | |
"commentable_type" => "App\Video", | |
]); | |
} | |
/** @test */ | |
public function videos_database_has_expected_columns() | |
{ | |
$this->assertTrue( | |
Schema::hasColumns('videos', [ | |
'id','user_id', 'title', 'description', 'size', 'length' | |
]), 1); | |
} | |
/** @test */ | |
public function a_video_morphs_many_comments() | |
{ | |
$this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $this->video->comments); | |
} | |
} |
Files relevant to this test 👇
**MODEL FILES(App/)
-- User.php
-- Post.php
-- Video.php
-- Comment.php
**MIGRATION FILES(database/migrations/)
-- 2019\09\28\211240\create\videos\table.php
-- 2019\09\28\221627\add\commentable\id\and\commentable\type\to\comments\table.php
**MODEL FACTORY FILES(database/factories/)
-- VideoFactory.php
**UNIT TEST FILES(tests/Unit/)
-- PostsTest.php
-- VideosTest.php
-- CommentsTest.php
- Many-to-Many (Polymorphic)
In progress…
Kindly check back soon
- 👉Part 1: Intro and Schema Tests
- 👉Part 2: Testing Basic Model Relationships
- ⏯Part 3: Testing Polymorphic Relationships
Top comments (1)
Hello, many thanks for these articles!
I appreciate it!
Waiting for the next one: "Many-to-Many (Polymorphic)".