Does anyone have any suggestions on what I'm doing wrong in my test?
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);
}
What error message are you getting?
Its saying that $this->user() is null
Which doesn't make sense because I'm logged in a user.
What happens if you replace the return with
return Auth::user()->can('update', $venue);
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?
Possibly but I'm still a beginner in Mockery. So I don't know what all I can take advantage of with partial mocks.
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.
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!
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?
Because it cannot get the user, it will just return:
Error : Call to a member function can() on null
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);
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);
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.
Worth noting, my sign in method is basically just a helper to find a user and do $this->actingAs();
@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.
It comes from authorizable (I think), but I could not get that to work :(
@drewdan I see there are two authorizabletraits. One fromIlluminate\Foundation\Auth\Access;and another fromIlluminate\Contracts\Auth\Access`
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());
That is asserting the authorization passed and the relevant policy was hit and returns what you wanted it too.
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 :(
@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);
}
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.
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.
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!
The important bit there is getting the mock, and then binding into the service layer with
app()->offsetSet(LessonPolicy::class, $mock);
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());
}
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
@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.
I found this:
I've not been able to test it, but it looks promising
Please or to participate in this conversation.