Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jrdavidson's avatar

Mocking authorization can method

I'm trying to unit test a form request class authorize method which I've included below. I'm wanting to do it this way so that I can check the policy update method separately and that way I know that what comes out of there is based on the user role and such. Does anyone see anything wrong with doing it in this manner? Also am I mocking the correct class here?

public function authorized_returns_true_when_authenticated_user_can_update_venue()
{
    $user = UserFactory::new()->make();

    $loggedInUser = $this->actingAs($user);

    Mockery::mock($loggedInUser)->shouldReceive('can')->withArgs(['update', $user])->andReturn(true);

    $subject = new UpdateRequest();

    $this->assertFalse($subject->authorize());
}
public function authorize()
{
    $venue = $this->route('venue');

    if (! Auth::check()) {
        return false;
    }

    return $this->user()->can('update', $venue);
}
0 likes
34 replies
jrdavidson's avatar

Does anyone have any suggestions on what I'm doing wrong in my test?

jrdavidson's avatar

Which doesn't make sense because I'm logged in a user.

drewdan's avatar

What happens if you replace the return with

return Auth::user()->can('update', $venue);
drewdan's avatar

Would it not be better to make a partial mock on the RequestValidator?

Then authorize will return true when its called and you can go about testing the validator stuff?

jrdavidson's avatar

Possibly but I'm still a beginner in Mockery. So I don't know what all I can take advantage of with partial mocks.

jrdavidson's avatar

What I'm wanting to make sure is that it checks that the authenticated user has a call to the can method with the update parameter so that I can check the policy method separately.

drewdan's avatar

I just spent some time trying to write a test so I could access the user in the authorize, the only way I can make it work is by using the Auth facade. I am a little stumped!

jrdavidson's avatar

Is there a way I can do the partial mock of just saying that when it hits that can method that it will have a arg of update and return true?

drewdan's avatar

Because it cannot get the user, it will just return: Error : Call to a member function can() on null

jrdavidson's avatar

I've updated my test and the actual request class to have the following, however, I'm receiving the following error stack.

Illegal offset type in isset or empty

  at vendor/laravel/framework/src/Illuminate/Container/Container.php:435
    431|      * @return void
    432|      */
    433|     protected function removeAbstractAlias($searched)
    434|     {
  > 435|         if (! isset($this->aliases[$searched])) {
    436|             return;
    437|         }
    438| 
    439|         foreach ($this->abstractAliases as $abstract => $aliases) {

      +2 vendor frames 
  3   tests/Unit/Http/Requests/Venues/UpdateRequestTest.php:60
      Illuminate\Foundation\Testing\TestCase::mock(Object(Tests\Unit\Http\Requests\Venues\UpdateRequestTest))
$this->mock($loggedInUser)->shouldReceive('can')->withArgs(['update', $user])->andReturn(true);
return Auth::user()->can('update', $venue);
trungtranqn91's avatar

Method can() came from which class? I think that should be :

 $this->mock('Class method Can came from ')->shouldReceive('can')->withArgs(['update', $user])->andReturn(true);
drewdan's avatar

Remove the mock:

public function testAuthorizeMethodOnFormRequest() {

	$this->signIn();
	$storeRequest = new StoreRequest;
	$this->assertTrue($storeRequest->authorize());
}

My form request:

public function authorize() {
	return Auth::user()->can('lesson.index');
}

I can get a test to pass that way.

drewdan's avatar

Worth noting, my sign in method is basically just a helper to find a user and do $this->actingAs();

jrdavidson's avatar

@drewdan I'm confused then. I"m trying to test that the the authorize method has a call to a specific method on a policy.

drewdan's avatar

It comes from authorizable (I think), but I could not get that to work :(

jrdavidson's avatar

@drewdan I see there are two authorizabletraits. One fromIlluminate\Foundation\Auth\Access;and another fromIlluminate\Contracts\Auth\Access`

drewdan's avatar

Ok, so in my store request:

class StoreRequest extends FormRequest {

	public function authorize() {
		return Auth::user()->can('viewAny', Lesson::class);
	}

Which uses a policy

and in my test

	public function test() {

		$this->signIn();
		$storeRequest = new StoreRequest;
		$this->mock(LessonPolicy::class)->shouldReceive('viewAny')->andReturn('true');
		$this->assertTrue($storeRequest->authorize());
1 like
drewdan's avatar

That is asserting the authorization passed and the relevant policy was hit and returns what you wanted it too.

drewdan's avatar

The one of those is an interface, and the other is a class implementing the interface. I did try to mock them, but it did not seem to work :(

jrdavidson's avatar

@drewdan It appears it shoudl work and fixes the error I was getting however no idea why I'm getting the failure of the assertion. It says the asserion is coming out False instead of True.

 /** @test */
    public function authorized_returns_true_when_authenticated_user_can_update_venue()
    {
        $user = UserFactory::new()->make();

        $this->actingAs($user);

        $subject = new UpdateRequest();

        $this->mock(VenuePolicy::class)->shouldReceive('update')->andReturn(true);

        $this->assertTrue($subject->authorize());
    }
public function authorize()
    {
        $venue = $this->route('venue');

        if (! Auth::check()) {
            return false;
        }

        return Auth::user()->can('update', $venue);
    }
drewdan's avatar

Looks like mine was giving me false positives. It seems you cannot mock the policies. Possibly because it has already been bound to the container.

The only thing I can think of suggesting at this stage is:

$this->signIn(); //user with permissions
$storeRequest = new StoreRequest;
$this->assertTrue($storeRequest->authorize());
$this->signin('anotheruser'); //without permission
$this->assertFalse($storeRequest->authorize());
```PHP

creating two users, one with permission, and one without and testing them this way.
jrdavidson's avatar

Can we not pull it out of the container with $this->app? I don't really know much about this and just asking questions based on your responses.

drewdan's avatar

I think I got it! Not 100%, but I think!

$this->signIn(); //user with permissions
		$mock = $this->mock(LessonPolicy::class)->makePartial()->shouldReceive('viewAny')->andReturn(true)->getMock();
		app()->offsetSet(LessonPolicy::class, $mock);
		$storeRequest = new StoreRequest;
		$this->assertTrue($storeRequest->authorize());

When doing this, I could change the return, and it did affect the test properly!

drewdan's avatar

The important bit there is getting the mock, and then binding into the service layer with

app()->offsetSet(LessonPolicy::class, $mock);
jrdavidson's avatar

I've never seen offsetSet before. The only methods I've seen before off of the app() helper is bind and instance. I've updated my test to the following which should pass but says the authorize returns false for some reason.

/** @test */
public function authorized_returns_true_when_authenticated_user_can_update_venue()
{
    $user = UserFactory::new()->make();

    $this->actingAs($user);

    $mock = $this->mock(VenuePolicy::class)->makePartial()->shouldReceive('update')->andReturn(true)->getMock();
    app()->offsetSet(VenuePolicy::class, $mock);

    $subject = new UpdateRequest();

    $this->assertTrue($subject->authorize());
}
drewdan's avatar

In your authorization method:

$venue = $this->route('venue');

When you run this test, I guess the $venue is empty?

So when it gets to your policy, is it calling the right policy? Maybe comment the mock and app() and put a dd in your policy to see what the value of $venue is

jrdavidson's avatar

@drewdan It's coming back null because I don't have it set up to make the venue be an instance of a Venue through the requested route. Ah, that would make sense.

I just need to figure out how to set it up as an instance as that.

Next

Please or to participate in this conversation.