Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Unit Testing Laravel Models
Geni Jaho
Geni Jaho

Posted on • Edited on • Originally published atgenijaho.dev

     

Unit Testing Laravel Models

Some might consider Laravel model testing to be a gruesome task, especially when there's plenty of them in the codebase, and most of them are covered by other unit or feature tests. However, to get to that sweet 100% (or so) coverage, these tests need to be carried out. Plus, you can be much more specific when testing them in isolation.

The model we're testing goes like this, stripped of comments and similar relationships and attributes since we need to show only one of each thing we're testing.

<?phpnamespaceApp\Models;use...classPhotoextendsModel{useHasFactory;protected$fillable=['filename','model','datetime',...];protected$appends=['selected'];publicfunctiongetSelectedAttribute(){returnfalse;}publicstaticfunctioncategories(){return['smoking','food','brands',...];}publicfunctiontags(){foreach($this->categories()as$category){if($this->$category){foreach($this->$category->types()as$tag){if(is_null($this->$category[$tag])){unset($this->$category[$tag]);}}}}}publicfunctiontotal(){$total=0;foreach($this->categories()as$category){if($this->$category){// We dont want to include brands in total_litterif($category!=='brands'){$total+=$this->$category->total();}}}$this->total_litter=$total;$this->save();}publicfunctionboxes(){return$this->hasMany(Annotation::class);}publicfunctionowner(){return$this->belongsTo(User::class,'user_id');}/**     * Litter categories     */publicfunctionsmoking(){return$this->hasOne('App\Models\Litter\Categories\Smoking','id','smoking_id');}publicfunctionbrands(){return$this->hasOne('App\Models\Litter\Categories\Brand','id','brands_id');}/**     * More Litter categories     */}
Enter fullscreen modeExit fullscreen mode

Testing that the model has the expected columns

The first assertion that we want to do is to check if the model has all the important columns in the database. This will ensure that whatever happens, the models' schema will be accounted for.

useIlluminate\Support\Facades\Schema;useTests\TestCase;classPhotoTestextendsTestCase{publicfunctiontest_photos_database_has_expected_columns(){$this->assertTrue(Schema::hasColumns('photos',['id','user_id','filename','model','datetime','lat','lon','verification',...]));}}
Enter fullscreen modeExit fullscreen mode

Testing that the model has the expected attributes

ThePhoto model has aselected attribute, and we can't know for sure that it will always be there unless we write a test for it. The attribute simply returns false at the time of writing, which is the only thing we need to test.

...publicfunctiontest_a_photo_has_selected_attribute(){$photo=Photo::factory()->create();$this->assertFalse($photo->selected);}...
Enter fullscreen modeExit fullscreen mode

Testing the relationships on the model

When testing model relationships there are generally two things I want to test. The first one is the relationship type or the class of the object being returned, and the second is checking whether the object returned is the correct one. This has proven to me to work well on many occasions, it's neither too strict nor too relaxed.

publicfunctiontest_a_photo_has_many_boxes(){$photo=Photo::factory()->create();$annotation=Annotation::factory()->create(['photo_id'=>$photo->id]);$this->assertInstanceOf(Collection::class,$photo->boxes);$this->assertCount(1,$photo->boxes);$this->assertTrue($annotation->is($photo->boxes->first()));}publicfunctiontest_a_photo_has_an_owner(){$owner=User::factory()->create();$photo=Photo::factory()->create(['user_id'=>$owner->id]);$this->assertInstanceOf(User::class,$photo->owner);$this->assertTrue($owner->is($photo->owner));}publicfunctiontest_a_photo_has_a_smoking_relationship(){$smoking=Smoking::factory()->create();$photo=Photo::factory()->create(['smoking_id'=>$smoking->id]);$this->assertInstanceOf(Smoking::class,$photo->smoking);$this->assertTrue($smoking->is($photo->smoking));}
Enter fullscreen modeExit fullscreen mode

Testing the methods on the model

Notice thePhoto has acategories method. It simply returns an array of strings, so we make a simple assertion for that.

...publicfunctiontest_a_photo_has_categories(){$photo=Photo::factory()->create();$this->assertIsArray($photo->categories());$this->assertNotEmpty($photo->categories());}...
Enter fullscreen modeExit fullscreen mode

The next stop is thetags method. What it does is basically strips the empty tags off of every category relationship of the model. Since we're unit testing, we don't need to know why, we just need to know that it works. So:

publicfunctiontest_a_photo_removes_empty_tags_from_categories(){$smoking=Smoking::factory(['butts'=>1,'lighters'=>null])->create();$brands=Brand::factory(['walkers'=>1,'amazon'=>null])->create();$photo=Photo::factory()->create(['smoking_id'=>$smoking->id,'brands_id'=>$brands->id]);// As a sanity check, we first test that// the current state is as we expect it to be$this->assertEquals(1,$photo->smoking->butts);$this->assertEquals(1,$photo->brands->walkers);$this->assertArrayHasKey('lighters',$photo->smoking->getAttributes());$this->assertArrayHasKey('amazon',$photo->brands->getAttributes());$photo->tags();$this->assertEquals(1,$photo->smoking->butts);$this->assertEquals(1,$photo->brands->walkers);$this->assertArrayNotHasKey('lighters',$photo->smoking->getAttributes());$this->assertArrayNotHasKey('amazon',$photo->brands->getAttributes());}
Enter fullscreen modeExit fullscreen mode

Thetotal method calculates the total number of litter present in aPhoto by summing the totals of each category. It should filter out the litter forbrands and for that reason we include an item for it. However, we test that it does not get added to the total number of items:

publicfunctiontest_a_photo_has_a_count_of_total_litter_in_it(){$smoking=Smoking::factory(['butts'=>1])->create();$brands=Brand::factory(['walkers'=>1])->create();$photo=Photo::factory()->create(['smoking_id'=>$smoking->id,'brands_id'=>$brands->id]);$photo->total();// Brands are not calculated$this->assertEquals($smoking->total(),$photo->total_litter);}
Enter fullscreen modeExit fullscreen mode

The code used for illustration is taken from theOpenLitterMap project. They're doing a great job creating the world's most advanced open database on litter, brands & plastic pollution. The project is open-sourced and would love your contributions, both as users and developers.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Full-stack web developer with a passion for software architecture and cloud computing.
  • Joined

More fromGeni Jaho

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp