codenco-dev / eloquent-model-tester
Test easier your Eloquent Models in your Laravel Project
Installs: 5 922
Dependents: 1
Suggesters: 0
Security: 0
Stars: 21
Watchers: 2
Forks: 1
Open Issues: 4
Requires
- php: ^8.0
- illuminate/support: ^8.0|^9.0|^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^6.0 || ^7.0 || ^8.0 || ^9.0
- phpunit/phpunit: ^9.0|^10.0|^11.0
README
This package allows you to test your models about table structures, relations and more
Installation
You can install the package via composer:
composer require codenco-dev/eloquent-model-tester --dev
Usage
To use this package, you have to generate factories for your models. (
See Factories Documentation)
You can generate one test file by model or for several. For your model MyModel
you can use this command for example:
php artisan make:model MyModel -mf php artisan make:test Models/MyModelTest
To be able to test database, don't forget RefreshDatabase
use statements.
namespace Tests\Feature\Models;
use App\MyModel;
use CodencoDev\EloquentModelTester\HasModelTester;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class MyModelTest extends TestCase
{
use RefreshDatabase;
use HasModelTester;
public function test_have_my_model_model()
{
//...
}
}
For more simplicity, you can put the RefreshDatabase
use statement in tests/TestCase.php
file
Test of structure and of fillable / guarded
With this structure
users
id - integer
name - string
email - string
password - string
remember_token - string
other_field - string
created_at - timestamp
updated_at - timestamp
you can test if you have all the fields you need and if they are fillable or guarded.
class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasColumns(['id','name','email','password','remember_token']) ->assertHasColumnsInFillable(['name','password']) ->assertHasColumnsInGuarded(['remember_token']) ->assertHasTimestampsColumns(); } }
The function assertHasColumns()
only checks if the values provided are in the schema of the table. If you want to
ensure that no other columns are present, you can use the stricter assertHasOnlyColumns()
assertion to check that *
only* the column names provided are present in the table.
N.B. When using this you will need to provide the created_at
and updated_at
fields if they are present in the
database table.
The functions assertHasColumnsInFillable()
and assertHasColumnsInGuarded()
only check if the $fillable
and $guarded
arrays contain the values passed. If you want to ensure that no other entries are in the $fillable
and $guarded
arrays, then you can use the stricter assertHasOnlyColumnsInFillable()
and assetHasOnlyColumnsInGuarded()
functions as follows:
class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasOnlyColumns(['id','name','email','password','remember_token', 'created_at', 'updated_at']) // Will fail as missing 'other_field'. ->assertHasOnlyColumnsInFillable(['name','password']) ->assertHasOnlyColumnsInGuarded(['remember_token']); } }
To further confirm that only a set of columns can be filled, you can use the assertCanOnlyFill()
assertion to confirm
this. This assertion confirms that the fields provided are the only columns that can be filled, by confirming that these
are the only values in the $fillable
array and they don't appear in the $guarded
array.
class User extends Model { $fillable = ['name', 'password', 'email']; $guarded = ['remember_token']; } class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasColumns(['id','name','email','password','remember_token']) ->assertCanOnlyFill(['name','password']); // Will fail as 'email' is in the fillable array. } }
To confirm that a field does not appear in both the $fillable
and $guarded
arrays by mistake there is
the assertNoGuardedAndFillableFields()
assertion that checks that no entry appears in both:
class User extends Model { $fillable = ['name', 'password', 'email']; $guarded = ['email']; } class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertNoGuardedAndFillableFields(); // Will fail as 'email' is in both the fillable & guarded arrays. } }
To check for soft delete deleted_at
column on your model, you can use the assertHasSoftDeleteTimestampColumns()
assertion:
user:
id - integer
name - string
deleted_at - timestamp
use Illuminate\Database\Eloquent\SoftDeletes; class User extends Model { use SoftDeletes; } class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasSoftDeleteTimestampColumns(); } }
HasOne et BelongsTo
You can test relations of your models. For example, with this structure
user
id - integer
name - string
phones
id - integer
number - string
user_id - integer
you can use assertHasHasOneRelation()
and assertHasBelongsToRelations
methods like this:
class UserTest extends TestCase { use HasModelTestor; public function test_have_category_model() { $this->modelTestable(User::class) ->assertHasHasOneRelation(Phone::class); } } class PhoneTest extends TestCase { use HasModelTestor; public function test_have_customer_model() { $this->modelTestable(Phone::class) ->assertHasBelongsToRelation(User::class); } }
HasMany et BelongsTo
You can test relations of your models. For example, with this structure
categories
id - integer
name - string
customers
id - integer
name - string
category_id - integer
type_id - integer
you can use assertHasHasManyRelations
and assertHasBelongsToRelations
methods like this
class CategoryTest extends TestCase { use HasModelTestor; public function test_have_category_model() { $this->modelTestable(Category::class) ->assertHasHasManyRelation(Customer::class); } } class CustomerTest extends TestCase { use HasModelTestor; public function test_have_customer_model() { $this->modelTestable(Customer::class) ->assertHasBelongsToRelation(Category::class); } }
If you don't use Laravel naming convention, you may also override the relation and local keys (for belongsTo relation)
by passing
additional arguments to the assertHasHasManyRelations
and assertHasBelongsToRelations
methods
$this->modelTestable(Customer::class) ->assertHasBelongsToRelation(Category::class,'category','category_id'); $this->modelTestable(Category::class) ->assertHasHasManyRelation(Customer::class,'customers');
If you have several relations, you can chain methods like this:
$this->modelTestable(Customer::class) ->assertHasBelongsToRelation(Category::class) ->assertHasBelongsToRelation(OtherModel::class);
HasManyThrough relations
You can test has many through relations of your models. For example, with this structure
customers
id - integer
name - string
locations
id - integer
customer_id - integer
orders
id - integer
location_id - integer
you can use assertHasHasManyThroughRelations
method like this
class CustomersTest extends TestCase { use HasModelTestor; public function test_have_orders_model() { $this->modelTestable(Customer::class) ->assertHasHasManyThroughRelation(Order::class, Location::class); } }
If you don't use Laravel naming convention, you may also override the relation and foreign keys by passing additional
arguments to the assertHasHasManyThroughRelations
method
$this->modelTestable(Customer::class) ->assertHasHasManyThroughRelation(Order::class, Location::class, 'sales', 'prefix_customer_id', 'prefix_location_id', 'firstPrimaryKey', 'secondPrimaryKey');
Attention: as there is no official inverse of this relationship, it is not possible to use this assertion in the
reverse, i.e., in the orders
model checking for a customers
relationship.
Many to Many relations
You can test your ManyToMany relations with the ManyToManyRelationsTestable
trait.
users
id - integer
name - string
roles
id - integer
name - string
role_user
user_id - integer
role_id - integer
class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasManyToManyRelation(Role::class); } } class RoleTest extends TestCase { use HasModelTestor; public function test_have_role_model() { $this->modelTestable(User::class) ->assertHasManyToManyRelation(User::class); } }
You can override the relation argument too :
$this->modelTestable(User::class) ->assertHasManyToManyRelation(User::class,'users');
Morph Relations
If you have a Morph Relation,
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
you can use assertHasBelongsToMorphRelations
and assertHasHasManyMorphRelations
methods like this
class PostTest extends TestCase { use HasModelTestor; public function test_have_post_model() { $this->modelTestable(Post::class) ->assertHasHasManyMorphRelation(Comment::class,'comments'); } } class VideoTest extends TestCase { use HasModelTestor; public function test_have_video_model() { $this->modelTestable(Video::class) ->assertHasHasManyMorphRelation(Comment::class,'comments'); } } class CommentTest extends TestCase { use HasModelTestor; public function test_have_morph_model_model() { $this->modelTestable(Comment::class) ->assertHasBelongsToMorphRelation(Post::class,'commentable') ->assertHasBelongsToMorphRelation(Video::class,'commentable'); } }
MorphOne Relations
If you have a Morph One Relation,
posts
id - integer
title - string
body - text
users
id - integer
title - string
images
id - integer
url - string
imageable_id - integer
imageable_type - string
you can use assertHasBelongsToMorphRelations
and assertHasMorphOneRelations
methods like this
class PostTest extends TestCase { use HasModelTestor; public function test_have_post_model() { $this->modelTestable(Post::class) ->assertHasMorphOneRelation(Image::class); } } class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasMorphOneRelation(Image::class, 'avatar'); } } class ImageTest extends TestCase { use HasModelTestor; public function test_have_image_model() { $this->modelTestable(Image::class) ->assertHasBelongsToMorphRelation(Post::class,'imageable') ->assertHasBelongsToMorphRelation(User::class,'imageable'); } }
Pivot and table without Model
You can test if a table contains columns with the tableTestable
method
class MyPivotTest extends TestCase { public function test_have_table_without_model() { $this->tableTestable('pivot_table') ->assertHasColumns(['first_model_id','second_model_id','other_property']); } }
Model Scopes
You can test if a model has a query scope defined using the assertHasScope()
assertion:
class User extends Model { /** * Scope a query to only include popular users. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopePopular($query) { return $query->where('votes', '>', 100); } } class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasScope('popular'); } }
Of course if you're using Dynamic Scopes then assertHasScope()
takes the arguments to pass to the query scope:
class User extends Model { /** * Scope a query to only include users of a given type. * * @param \Illuminate\Database\Eloquent\Builder $query * @param mixed $type * @return \Illuminate\Database\Eloquent\Builder */ public function scopeOfType($query, $type) { return $query->where('type', $type); } } class UserTest extends TestCase { use HasModelTestor; public function test_have_user_model() { $this->modelTestable(User::class) ->assertHasScope('ofType', 'admin'); } }
Available Assertions
Testing
composer test
Changelog
Please see CHANGELOG for more information about what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email dthomas@codenco.fr or contact me on Twitter DomThomasEs instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.
Laravel Package Boilerplate
This package was generated using the Laravel Package Boilerplate.