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

dorvidas's avatar

Testing that Exception was thrown

Trying to add some DDT. Want to test if Exception is trhow. See the code bellow:

public function testCanAccessSmth()
{
    //Auth::loginUsingId($user_1->id);
    $this->setExpectedException('Exception');
    $this->visit('/page');
}

Running PHPUnit shows errors, because middleware throws them. But thats what I expcet. Shouldn't it give me green. Larvel documentation is not very specific about that http://laravel.com/docs/5.1/testing

How you tackle that?

0 likes
18 replies
dorvidas's avatar

toniperic that does not work. I tried putting this to try catch statement, but it does not help, it seems that it never goes to catch statement. I simply decided in my middleware not to call abort() but just simply redirect with error message, so I validate whether I was redirected and wheter I see error on page.

$this->visit('/url)->see('Access denied')->seePageIs('/orders');

Here is middleware

return redirect('/orders')->withErrors(['msg'=>'Access denied']);

toniperic's avatar

@dorvidas if you're expecting an exception to be thrown then that does work, no need to use try/catch within tests.

It obviously won't work if you're testing for a redirection.

dorvidas's avatar

@toniperic I will try to set up a basic example and show that it does not work later on. Will try to call abort() function in some route and check whether it is beeing catched when calling "visit" method in test.

martinbean's avatar

@dorvidas I think you’re misunderstanding. You don’t try and handle the exception in your test case. Instead, you add the @​expectedException annotation @toniperic showed you. If that exception is thrown as part of the test, the test passes.

3 likes
dorvidas's avatar

@martinbean No I did understand. The test didn't pass, i did not get green. When adding throw new \Exception('fail') in test it works, but when doing the same in middleware and doing ->visit('/path') in test it fails. I am sure I will figure later on. I will inform how it went... Thansk by the way...

Rob_vH's avatar

@dorvidas Your situation tells you that it is not bubbling up to the test case. Your task now is to figure out why. Are you catching that exception in the middleware or a handler?

dorvidas's avatar

Ok I am back. So let's say I have simple route:

Route::get('test', function () {
   abort('500', 'Aborting');
});

Then I have simple test:

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class TestTest extends TestCase
{
    /**
     * A basic test example.
     * @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
     *
     * @return void
     */
    public function testExample()
    {
        $this->visit('/test');
    }
}

Below is output of PHPUNIT:

vagrant@timonlinelaravel:/vagrant$ phpunit
PHPUnit 4.8.16 by Sebastian Bergmann and contributors.

....F                                                               5 / 5 (100%)

Time: 5.13 seconds, Memory: 22.50Mb

There was 1 failure:

1) TestTest::testExample
A request to [http://timonlinelaravel.app/test] failed. Received status code [500].

/vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php:166
/vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php:64
/vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php:45
/vagrant/tests/TestTest.php:17
/home/vagrant/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:154
/home/vagrant/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:105

Caused by
exception 'Symfony\Component\HttpKernel\Exception\HttpException' with message 'Aborting' in /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:882
Stack trace:
#0 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php(21): Illuminate\Foundation\Application->abort('500', 'Aborting', Array)
#1 /vagrant/app/Http/routes.php(134): abort('500', 'Aborting')
#2 [internal function]: App\Providers\RouteServiceProvider->{closure}()
#3 /vagrant/vendor/laravel/framework/src/Illuminate/Routing/Route.php(155): call_user_func_array(Object(Closure), Array)
#4 /vagrant/vendor/laravel/framework/src/Illuminate/Routing/Route.php(130): Illuminate\Routing\Route->runCallable(Object(Illuminate\Http\Request))
#5 /vagrant/vendor/laravel/framework/src/Illuminate/Routing/Router.php(704): Illuminate\Routing\Route->run(Object(Illuminate\Http\Request))
#6 [internal function]: Illuminate\Routing\Router->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#7 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(139): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#8 [internal function]: Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#9 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#10 /vagrant/vendor/laravel/framework/src/Illuminate/Routing/Router.php(706): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#11 /vagrant/vendor/laravel/framework/src/Illuminate/Routing/Router.php(671): Illuminate\Routing\Router->runRouteWithinStack(Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request))
#12 /vagrant/vendor/laravel/framework/src/Illuminate/Routing/Router.php(631): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request))
#13 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(236): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request))
#14 [internal function]: Illuminate\Foundation\Http\Kernel->Illuminate\Foundation\Http\{closure}(Object(Illuminate\Http\Request))
#15 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(139): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#16 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(50): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#17 [internal function]: Illuminate\Foundation\Http\Middleware\VerifyCsrfToken->handle(Object(Illuminate\Http\Request), Object(Closure))
#18 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#19 /vagrant/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#20 [internal function]: Illuminate\View\Middleware\ShareErrorsFromSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#21 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#22 /vagrant/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(62): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#23 [internal function]: Illuminate\Session\Middleware\StartSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#24 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#25 /vagrant/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#26 [internal function]: Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse->handle(Object(Illuminate\Http\Request), Object(Closure))
#27 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#28 /vagrant/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(59): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#29 [internal function]: Illuminate\Cookie\Middleware\EncryptCookies->handle(Object(Illuminate\Http\Request), Object(Closure))
#30 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#31 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php(42): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#32 [internal function]: Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode->handle(Object(Illuminate\Http\Request), Object(Closure))
#33 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#34 [internal function]: Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#35 /vagrant/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#36 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(122): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#37 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(87): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#38 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/CrawlerTrait.php(394): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#39 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php(62): Illuminate\Foundation\Testing\TestCase->call('GET', 'http://timonlin...', Array, Array, Array)
#40 /vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php(45): Illuminate\Foundation\Testing\TestCase->makeRequest('GET', '/test')
#41 /vagrant/tests/TestTest.php(17): Illuminate\Foundation\Testing\TestCase->visit('/test')
#42 [internal function]: TestTest->testExample()
#43 /vagrant/vendor/phpunit/phpunit/src/Framework/TestCase.php(909): ReflectionMethod->invokeArgs(Object(TestTest), Array)
#44 /vagrant/vendor/phpunit/phpunit/src/Framework/TestCase.php(768): PHPUnit_Framework_TestCase->runTest()
#45 /vagrant/vendor/phpunit/phpunit/src/Framework/TestResult.php(612): PHPUnit_Framework_TestCase->runBare()
#46 /vagrant/vendor/phpunit/phpunit/src/Framework/TestCase.php(724): PHPUnit_Framework_TestResult->run(Object(TestTest))
#47 /vagrant/vendor/phpunit/phpunit/src/Framework/TestSuite.php(747): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
#48 /vagrant/vendor/phpunit/phpunit/src/Framework/TestSuite.php(747): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult))
#49 /vagrant/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(440): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult))
#50 /home/vagrant/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(154): PHPUnit_TextUI_TestRunner->doRun(Object(PHPUnit_Framework_TestSuite), Array)
#51 /home/vagrant/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(105): PHPUnit_TextUI_Command->run(Array, true)
#52 /home/vagrant/.composer/vendor/phpunit/phpunit/phpunit(47): PHPUnit_TextUI_Command::main()
#53 {main}
FAILURES!
Tests: 5, Assertions: 8, Failures: 1.

So test does not pass. I tried @expectedException declaration with and without backslash in front. Official documentation does not tell how to do @expectedException tests. You can try it yourself it is easy to setup

dorvidas's avatar

Well throwing Exception in test works

class TestTest extends TestCase
{
    /**
     * A basic test example.
     * @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
     *
     * @return void
     */
    public function testExample()
    {
        throw new \Symfony\Component\HttpKernel\Exception\HttpException(500);
        //$this->visit('/test');
    }
}
dorvidas's avatar

Ok so this works:

    /**
     * A basic test example.
     * @return void
     */
    public function testExample()
    {
        $this->setExpectedException('Symfony\Component\HttpKernel\Exception\HttpException'); //Or we can define in PHPDocBlocks
        $this->visit('/test');
        throw $this->response->exception;
    }

Thing to notice that if you terminate your app with abort() method, and pass other statuses than 200 it will stop executing code and Exception won't be re-thrown.

Ltloafer's avatar

Hi,

If I use the

$this->setExpectedException('Symfony\Component\HttpKernel\Exception\HttpException'); //Or we can define in PHPDocBlocks

I just get

Fatal error: Uncaught exception 'Illuminate\Contracts\Container\BindingResolutionException' with message 'Target [Illuminate\Contracts\Debug\ExceptionHandler] is not instantiable.'

Anybody else get this??

KamalKhan's avatar

Have you tried this:

class TestTest extends TestCase
{
    /**
     * A basic test example.
     * @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
     *
     * @return void
     */
    public function testExample()
    {
        $this->get('/test');
    }
}

Notice the method get instead of visit.

ivanv's avatar

I also bumped on this problem...

When using $this->visit('/test') I can't seem to set any expected exceptions. When using $this->get('/test') like in the example of @KamalKhan the test still fails:

Failed asserting that exception of type "\Symfony\Component\HttpKernel\Exception\HttpException" is thrown.

I could check for a status code instead of an exception though:

/**
 * A basic test example.
 *
 * @return void
 */
public function testExample()
{
    $this->get('/test')->seeStatusCode(500);
}

A question that's burning in the back of my mind... Should we even be asserting exceptions in this kind of (acceptance?) tests? Or should we just be testing success cases, since exceptions should probably never be thrown to the end user, but handled gracefully?

1 like
ivanv's avatar

I found how to let the exceptions bubble up to the tests thanks to this video of @adamwathan !

public function testExample()
{
    // Don't chain this... it doesn't return $this!
    $this->setExpectedException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
    
    $this->visit('/test')->seeStatusCode(404);
}

I just rethrow the exception in app/Exceptions/Handler.php:

public function render($request, Exception $e)
{
    throw $e;
    return parent::render($request, $e);
}

But in the video Adam also shows how to create a helper method in TestCase to achieve this in your tests... You obviously don't want to rethrow in the real Handler class :)

So I've added these to helpers to my TestCase:

/**
 * Disable Laravel's exception handling.
 *
 * @return $this
 */
protected function disableExceptionHandling()
{
    app()->instance(Handler::class, new class extends Handler {
        public function __construct() {}
        public function report(Exception $e) {}
        public function render($request, Exception $e)
        {
            throw $e;
        }
    });

    return $this;
}

/**
 * Set an expected exception.
 *
 * @param string $class
 *
 * @return $this
 */
protected function expectException($class)
{
    $this->disableExceptionHandling();
    $this->setExpectedException($class);

    return $this;
}

I use the second method to chain my tests, without the need to explicitly call the disable method, just for convenience...

public function testExample()
{
    $this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class)
        ->visit('/test')
        ->seeStatusCode(404);
}
1 like
MiCc83's avatar

What about:

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if (php_sapi_name() === 'cli'){
        throw $exception;
    }

    return parent::render($request, $exception);
}
1 like
jplew's avatar

Thanks @ivanv for the solution.

I'm not using PHP 7 on my server so I can't use anonymous classes unfortunately, so I needed an alternative.

I decided to set an environment variable inside the scope of each test I need to disable the default error-handling:

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Exception $e
     * @return void
     */
    public function report(Exception $e)
    {
        if (env('PHPUNIT_TESTING')) {
            throw $e;
        }
        parent::report($e);
    }

Then in my test:

    /** 
    * @test 
    * @expectedException App\Exceptions\ParamNotSent
    */
    public function check_for_param_not_sent_exception()
    {
        putenv("PHPUNIT_TESTING=true");

        // run test

        putenv("PHPUNIT_TESTING=false");
    }
    ...
1 like
Robstar's avatar

@IVANV - Thanks for that, I was testing that an AuthorizationExceptionwas thrown, without any luck. I'd actually copied in the disableExceptionHandling() helper from the forrum series and forgot it was there.

I'd personally say this is best was to disable handing. I certainly don't think it particularly good to be editing the main Laravel exception handler with the conditional check.

1 like

Please or to participate in this conversation.