Laravel Unit Test Relationship For Model
Bài đăng này đã không được cập nhật trong 4 năm
Lời mở đầu
Trong quá trình phát triển dự án một điều mà chúng ta thường xuyên bỏ qua và ít khi để ý đó là kiểm soát lỗi hệ thống của chúng ta. Nhưng theo năm tháng ứng dụng của chúng ta ngày càng lớn, càng nhiều module được phát triển thì việc kiểm soát lỗi là vô cùng quan trọng. Khi nhận được yêu cầu thay đổi từ khách hàng chúng ta lại cuống cuồng vào đọc code và sửa lại những đoạn code trước đó. Tuy nhiên đoạn code đó được sử dụng ở khá nhiều nơi, sửa chỗ này lại toang chỗ kia =)). Vậy làm sao để kiểm soát được sửa dòng này thì những chỗ nào sẽ toang? có ảnh hưởng gì tới những chỗ khác không? Nếu dự án của bạn nhỏ thì không vấn đề gì những cứ tưởng tượng dự án của bạn khá lớn, mỗi tháng khách hàng trả cho bạn 100 tỏi và hiện tại đang có vài triệu người dùng thì chắc hẳn dự án của bạn cũng khá lớn rồi đấy. Sửa 1 dòng code thôi là ăn ngay incident như chơi. :v Vậy đấy, những lúc như vậy Unit Test sẽ phát huy tác dụng cực kì. Và hôm nay mình sẽ hướng dẫn các bạn viết test relationship trong model.
Nhìn lại xem 1 vài quan hệ phổ biến trong Laravel để chúng ta viết test cho nó nhé:
- One to One
- One to Many
- Many to Many
- Polymorphic Relations
- Many To Many Polymorphic Relations
Ok, hãy cùng bắt đầu thôi.
1. Quan hệ One to One (Quan hệ một - một)
Ví dụ chúng ta có Model User quan hệ một một với Product
hasOne()
public function product()
{
return $this->hasOne(Product::class);
}
Chúng ta sẽ viết test như sau:
public function test_user_has_one_product()
{
$user = factory(User::class)->create();
$product = factory(Product::class)->create(['user_id' => $user->id]);
$this->assertInstanceOf(Product::class, $user->product);
$this->assertEquals(1, $user->product->count());
}
Giải thích:
Ở đây chúng ta dùng factory tạo ra 1 user và 1 product cho user đó.
Nếu $user->product
trả về một instance của Product thì như vậy là đúng. Hoặc $user->product->count()
trả về 1 là đúng vì một user chỉ có một product.
Định nghĩa ngược lại belongsTo()
public function user()
{
return $this->belongsTo(User::class);
}
Chúng ta sẽ viết test như sau:
use Illuminate\Database\Eloquent\Relations\BelongsTo;
public function test_product_be_longs_to_user()
{
$user = factory(User::class)->create();
$product = factory(Product::class)->create(['user_id' => $user->id]);
// kiểm tra foreignkey có giống nhau không
$this->assertEquals('user_id', $product->user()->getForeignKey());
// kiểm tra instance BelongsTo
$this->assertInstanceOf(BelongsTo::class, $product->user());
}
Note: Từ phiển bản 5.8 trở lên Laravel đã sửa hàm getForeignKey()
thành getForeignKeyName()
rồi nhé.
2. Quan hệ One to Many (Quan hệ một nhiều)
hasMany()
Ví dụ chúng ta có Model User quan hệ một nhiều với Product
hasOne()
//Model User
public function products()
{
return $this->hasMany(Product::class);
}
//Model Product
public function user()
{
return $this->belongsTo(User::class);
}
Chúng ta sẽ viết test như sau:
public function test_user_has_many_products()
{
$user = factory(User::class)->create();
$product = factory(Product::class)->create(['user_id' => $user->id]);
// kiểm tra có phải là instance của HasMany không
$this->assertInstanceOf(HasMany::class, $user->products());
// kiểm tra foreignkey
$this->assertEquals('user_id', $user->products()->getForeignKeyName());
}
Định nghĩa quan hệ ngược lại là belongsTo
Xem ở bên trên mình đã viết rồi nhé.
3. Quan hệ Many to Many (Quan hệ nhiều nhiều)
Ví dụ: Chúng ta có model Book quan hệ nhiều nhiều với Category
// Model Book
public function categories()
{
return $this->belongsToMany(Category::class);
}
// Model Category
public function books()
{
return $this->belongsToMany(Book::class);
}
Chúng ta sẽ viết test như sau:
public function test_book_be_longs_to_many_categories()
{
// kiểm tra instance BelongsToMany
$this->assertInstanceOf(BelongsToMany::class, $book->categories());
// kiểm tra foreignKey
$this->assertEquals($foreignKey, $book->categories()->getForeignPivotKeyName());
// kiểm tra related pivot key
$this->assertEquals($relateKey, $book->categories()->getRelatedPivotKeyName());
// Càng nhiều so sánh thì test của chúng ta càng chính xác. Vì vậy hãy thêm số lượng test phù hợp
// với function của bạn nhé
}
4. Quan hệ Polymorphic Relations (Quan hệ đa hình)
4.1 Quan hệ One One
Ví dụ: Chúng ta có bảng post và product quan hệ với bảng image như sau. Bảng image sẽ lưu hình ảnh của post và product
posts
id - integer
title - string
products
id - integer
name - string
images
id - integer
url - string
imageable_id - integer
imageable_type - string
Và model của chúng sẽ như sau:
// Model Image
public function imageable()
{
return $this->morphTo();
}
// Model Product
public function image()
{
return $this->morphOne('App\Image', 'imageable');
}
// Model Post
public function image()
{
return $this->morphOne('App\Image', 'imageable');
}
Vậy là chúng ta sẽ cần test 2 relation là morphTo
và morphOne()
morphTo()
public function test_imageable_with_post()
{
$image = factory(Image::class)->create([
"imageable_id" => $user->id,
"imageable_type" => "App\Post",
]);
$this->assertInstanceOf(Post::class, $image->imageable);
}
// tương tự với product nhé
morphOne()
public funtion test_post_morph_one_image()
{
// kiểm tra instance MorphOne
$this->assertInstanceOf(MorphOne::class, $post->image());
//kiểm tra key type
$name = 'imageable';
$this->assertEquals($name . '_type', $post->image()->getMorphType());
$this->assertEquals($name . '_id';, $post->image()->getForeignKeyName());
}
4.2 Quan hệ One Many
Ví dụ: Chúng ta có 4 bảng là: post, product và comment. Quan hệ của chúng như sau:
posts
id - integer
title - string
products
id - integer
namestring
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
Và model của chúng sẽ như sau:
// Model Comment
public function commentable()
{
return $this->morphTo();
}
//Model Post
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
//Model Product
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
Vậy là chúng ta sẽ cần test 1relation là morphOne()
. morphTo
thì đã viết ở trên rồi.
Cũng tương tự như trên chúng ta làm như sau:
public function test_post_morph_many_comment()
{
$relation = $post->comment();
$name = 'commentable';
$this->assertInstanceOf(MorphMany::class, $relation);
$this->assertEquals($name . '_type', $relation->getMorphType());
$this->assertEquals($name . '_id', $relation->getForeignKeyName());
}
4.3 Quan hệ Many Many
Giả sử chúng ta có bảng tag sử dụng cho cả post và product.
posts
id - integer
title - string
products
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
Cấu trúc model sẽ như sau:
//Model Post
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
//Model Product
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
//Model Tag
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
public function products()
{
return $this->morphedByMany('App\Product', 'taggable');
}
Như vậy chúng ta sẽ cần test 2 relation là morphToMany()
và morrphedByMany()
morphToMany():
Tương tự như quan hệ nhiều nhiều chúng ta sẽ test như sau:
public function test_tags_morph_to_many_posts()
{
$relation = $tags->posts();
$name = 'taggable';
$this->assertInstanceOf(MorphToMany::class, $relation);
$this->assertEquals($name . '_type', $relation->getMorphType());
$this->assertEquals($name . '_id';, $relation->getForeignPivotKeyName());
$this->assertEquals($tags->getForeignKey(), $relation->getRelatedPivotKeyName());
}
morphedByMany():
public function test_products_morphed_by_many_tags()
{
$relation = $products->tags();
$related = $products;
$this->assertInstanceOf(MorphToMany::class, $relation);
$this->assertEquals($name . '_type', $relation->getMorphType());
$this->assertEquals($products->getForeignKey(), $relation->getForeignPivotKeyName());
$this->assertEquals($name . '_id', $relation->getRelatedPivotKeyName());
}
Vậy là xong. Khá dễ dàng.
Trong dự án chúng ta nên viết 1 file model test case chung và khi test model nào chúng ta extend từ file đó và chỉ cần gọi những function viết test mà chúng ta đã viết sẵn ra là xong.
Ví dụ:
class ModelTestCase extends TestCase
{
protected function assertHasManyRelation(
$relation,
Model $model,
Model $related,
$key = null,
$parent = null,
\Closure $queryCheck = null
) {
$this->assertInstanceOf(HasMany::class, $relation);
if (!is_null($queryCheck)) {
$queryCheck->bindTo($this);
$queryCheck($relation->getQuery(), $model, $relation);
}
if (is_null($key)) {
$key = $model->getForeignKey();
}
$this->assertEquals($key, $relation->getForeignKeyName());
if (is_null($parent)) {
$parent = $model->getKeyName();
}
$this->assertEquals($model->getTable() . '.' . $parent, $relation->getQualifiedParentKeyName());
}
}
// như vậy khi cần test quan hệ hasMany chúng ta gọi làm hàm này và truyền vào tham số cần thiết là xong.
Trên đây là cách viết test relationship cho model trong Laravel. Bài viết sau mình sẽ viết test cho controller, service và repository. Cảm ơn bạn đã đọc bài của mình.
All rights reserved