In a project I'm working on I needed to integrate authorization so I created policies for all of the models, installed Laravel Permission and added viewAll, view, create, update, delete permissions for every model. Policies simply check user permissions provided by a role, using this packaged offcourse.
After adding all the authorize calls to the controllers I ran tests and figured that I needed to add all those permissions to the tests to. So if I'm testing "creating an invoice" (for example), the authenticated user also needs to have permission 'create.invoices'.
Besides this being cumbersome, in the navigation menu I'm checking if a user can see the menu items provided. So when testing invoices, it will check the menu item for something else, like customers. Which in turn means I'd better seed all permissions before the test.... but then my tests take like 5 seconds each.
So whats the way to go here? How do you guys handle this and/or solved this?
I'd love to hear what others are doing too!
While the following isn't an example of mass amounts of seeded permissions, it's the only public repo I have for showing code examples right now; Here are two approaches used in my demo:
https://github.com/drbyte/spatie-permissions-demo/blob/master/tests/Feature/PostsTest.php
I've also got a project with 50 permissions and I also had this problem.
When creating permissions with Permission::create() it executes an SQL query for each permission, so that's N+1 problem. I've solved it by batch inserting all permissions in seeder:
private function createPermissions()
{
$permissions = collect($this->permissions)->map(function ($permission) {
return ['name' => $permission, 'guard_name' => 'web'];
});
Permission::insert($permissions->toArray());
}
My each test now runs 3 times faster (was ~1.2s, now ~350ms).
Another way to improve performance when seeding permissions is to use insert to populate the role_has_permissions table as follows.
Note that if your code needs Eloquent events to fire during seeding, this example won't work as it uses insertGetId and insert instead of Model::create.
$permissionsByRole = [
'admin' => ['restore posts', 'force delete posts'],
'editor' => ['create a post', 'update a post', 'delete a post'],
'viewer' => ['view all posts', 'view a post']
];
$insertPermissions = fn ($role) => collect($permissionsByRole[$role])
->map(fn ($name) => DB::table('permissions')->insertGetId(['name' => $name]))
->toArray();
$permissionIdsByRole = [
'admin' => $insertPermissions('admin'),
'editor' => $insertPermissions('editor'),
'viewer' => $insertPermissions('viewer')
];
foreach ($permissionIdsByRole as $role => $permissionIds) {
$role = Role::whereName($role)->first();
DB::table('role_has_permissions')
->insert(
collect($permissionIds)->map(fn ($id) => [
'role_id' => $role->id,
'permission_id' => $id
])->toArray()
);
}
Most helpful comment
I've also got a project with 50 permissions and I also had this problem.
When creating permissions with
Permission::create()it executes an SQL query for each permission, so that's N+1 problem. I've solved it by batch inserting all permissions in seeder:My each test now runs 3 times faster (was ~1.2s, now ~350ms).