Pixelairport's avatar

Test Spatie Permissions with Orchestra Testbench policy

I use spatie permissions in my package and want to test with orchestra. That means I need Workbench\Models\User::class instead of User\Models\User::class as user model. I set this in phpunit.xml:

<env name="AUTH_MODEL" value="Workbench\Models\User::class"/>

Now I want to test the policy. But the user model implements spatie permissions trait and then the user has the function $user->hasPermissionTo('viewAny').

The question is, how to make the policy now. In the past I had:

    use App\Models\User;

    public function viewAny(User $user): bool
    {
        if($user->hasPermissionTo(PostPermission::VIEW_ANY->value)) {
            return true;
        }

        return false;
    }

that is not possible because now I use workbench for test. The user model should come from my config. So I need to delete use App\Models\User;. AI and Google says I should use use Illuminate\Contracts\Auth\Authenticatable; instead, but then my function to check permission is not available. But how should I inject the correct user model at data binding? Or is it a normal way to delete the type in this situation? I mean ...

// do
public function viewAny($user): bool
// instead of
public function viewAny(Authenticatable $user): bool

Does anybody know, what is the best or the standard way here?

0 likes
3 replies
Pixelairport's avatar
Pixelairport
OP
Best Answer
Level 12

If also the AI does not answer, you know you ask a boring questions :P ....

I think I have the solution. The code above seems ok. If I inject different user models in test and live code I remove use Workbench\Models\User or use App\Models\User and in the function I do public function viewAny($user) without to define User before $user. This ist the function. For all who also use Spatie Permissions like me, you also have to set the guard as second parameter in the hasPersmissionTo method. It took me a bit time to find out. But I think this is the best workaround for Spatie Permissions with Policies in Orchestra Workbench.


    public function viewAny($user): bool
    {
        if($user->hasPermissionTo(PostPermission::VIEW_ANY->value, 'web')) {
            return true;
        }

        return false;
    }

LaryAI's avatar
Level 58

Great question! When building a Laravel package that leverages Spatie permissions and is tested with Orchestra Testbench, you have to consider that the user model may differ between the application and your tests. This is especially common if your package is agnostic to the user model.

Best-Practice Solution

1. Type-Hint the Contract or the Base Authenticatable

Instead of hard-coding the model in your policy like:

public function viewAny(User $user): bool

You can type-hint the broader contract, but as you noted, the Illuminate\Contracts\Auth\Authenticatable contract doesn't include hasPermissionTo() (which comes from the Spatie trait).

2. Use the Spatie Trait’s Interface (Recommended)

If you want to keep type safety, the best approach is to type-hint the Spatie\Permission\Contracts\Permission or your user class if you know it always implements the trait, but that’s not always practical when writing a package.

3. Rely on Duck Typing (No Type-Hinting)

Given the above, the de facto standard is:

  • Don’t type-hint anything on your user.
  • Or, at most, hint Authenticatable if you want minimal interface guarantee.

Your policy method will look like this:

public function viewAny($user): bool
{
    return method_exists($user, 'hasPermissionTo')
        && $user->hasPermissionTo(PostPermission::VIEW_ANY->value);
}

Or just, if you're sure only your models use this, and want the test to fail if not:

public function viewAny($user): bool
{
    return $user->hasPermissionTo(PostPermission::VIEW_ANY->value);
}

4. How to Get the Model Dynamically?

If you want to dynamically resolve the user model (i.e., from your package config), don’t type-hint at all, and let Laravel inject the authenticated user.

You might use:

$userModel = config('auth.providers.users.model', \App\Models\User::class);

But in policies, you don't manually resolve the model—Laravel will inject the current user instance.

5. Testbench and Spatie Permissions Setup

In your tests/TestCase.php or your setup code, make sure to configure the user model and the Spatie permissions package to use your test model:

protected function getEnvironmentSetUp($app)
{
    // Set correct user model
    $app['config']->set('auth.providers.users.model', \Workbench\Models\User::class);
    // Optionally set spatie permission model configs here as well if customized
}

Summary

  • It is perfectly normal not to type-hint the user in your policy when building packages.
  • If you want stricter typing, document that your user must implement the Spatie trait/interface.
  • Always set the config in your Testbench tests to use your test user model.
  • Laravel will inject the correct user model according to your config.

Example Policy

public function viewAny($user): bool
{
    return $user->hasPermissionTo(PostPermission::VIEW_ANY->value);
}

This works seamlessly for both your real app and your package tests.


References

Let me know if you have more questions!

kei_mx's avatar

Hello.

First of all, why use permissions in your package?

Please or to participate in this conversation.