Michael Dyrynda
Home Blog Podcasts
 
Integration testing controller authorisation in Laravel February 11th, 2016

Introduction

Laravel 5.1 introduce out-of-the-box authorisation, which I got a chance to use on a project recently.

In order to ensure that I correctly restricted access to areas, I set out to write some integration tests using the built-in functionality. It was at this point I started to hit stumbling blocks.

The Problem

By default, Laravel is configured (in app/Exceptions/Handler.php to not report any (Symfony) HttpExceptions. This means that using an expectedException annotation in your tests won't work, because Laravel is catching the exception and rendering it as a HTML response.

My first attempt at solving this involved checking if unit tests were being run and simply throwing the exception instead of rendering the output.

1// app/Exceptions/Handler.php
2 
3public function render($request, Exception $e)
4{
5 if (app()->runningUnitTests()) {
6 throw $e;
7 }
8}

The runningUnitTests() method simply checks if the current environment is set to testing, as it is by default when running tests in Laravel `.

This allowed me to use the expectedException annotation in my unit tests to tell PHP Unit I was expecting an exception of type Symfony\Component\HttpKernel\Exception\HttpException, which worked, but felt hacky.

The Laravel documentation notes an assertResponseStatus($code) method, which allows you to check your response returned a particular response code:

1/**
2 * @test
3 */
4public function it_checks_a_user_cannot_see_a_customer_they_do_not_belong_to()
5{
6 // Setup two customers and two users, associating one user to each
7 $this->actingAs($userOne)
8 ->visit(route('customers::show', $customerTwo->id))
9 ->assertResponseStatus(403);
10}

This seemed like it ought to be enough, but still an exception was returned when running tests:

1A request to [http://localhost/customers/2] failed. Received status code [403].

The Solution

Digging further into Laravel's source, I located the assertPageLoaded method, which checks that the response of a visit request has a response code of 200 using an assertEquals. If not, PHP Unit will throw an exception which is caught and thrown back as an instance of Illuminate\Foundation\Testing\HttpException

Now we're on to something!

1/**
2 * @test
3 * @expectedException Illuminate\Foundation\Testing\HttpException
4 */
5public function it_checks_a_user_cannot_see_a_customer_they_do_not_belong_to()
6{
7 // Setup two customers and two users, associating one user to each
8 
9 // You could use the following instead of the @expectedException annotation in the test docblock
10 $this->setExpectedException('Illuminate\Foundation\Testing\HttpException');
11 
12 $this->actingAs($userOne)
13 ->visit(route('customers::show', $customerTwo->id))
14 ->assertResponseStatus(403);
15}

Conclusion

After a bit of digging around, it seems that the solution was simple enough: use the assertResponseStatus method in combination with either the expectedException annotation or the setExpectedException method.

This feels more Laravel-like, but I'm not 100% certain this is the correct way to go about it. If you have any thoughts about this, I'd love to hear from you either in the comments below or via Twitter.

I'm a real developer ™
Michael Dyrynda

@michaeldyrynda

I am a software developer specialising in PHP and the Laravel Framework, and a freelancer, blogger, and podcaster by night.

Proudly hosted with Vultr

Syntax highlighting by Torchlight