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) HttpException
s. 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.php2 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 docblock10 $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.