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

axeloz's avatar

How to catch Exception in Unit testing

Hello,

I'm just looking for the best way of catching exceptions in Unit Testing. I have this piece of code (truncated):

	public function storePayment(Request $request) {
		try {
			[...]

			// Changing default payment on Stripe then charging
			$user->updateDefaultPaymentMethod($request->setup_method);

		    // Other actions then
		    // Sending confirmation email
		}
		catch (Exception $e) {
			report($e);
			return back()
				->withInput()
				->with('error', 'We could not record your payment')
			;
		}
}

My Unit test works as excepted, it fails ! But it fails because I'm doing multiple tests including checking that an email has been sent at the end. It fails because of the consequence of the exception (email not sent), not because of the exception itself. Is there a way to catch the exception itself and thus know what led to the error?

I don't know if it makes sense. Thanks

0 likes
6 replies
martinbean's avatar
Level 80

But [my unit test] fails because I'm doing multiple tests including checking that an email has been sent at the end. It fails because of the consequence of the exception (email not sent), not because of the exception itself.

@axeloz Then that’s not a unit test; that’s very much a feature test if you’re testing that many things.

Also, just gobbling exceptions in your controller action or whatever that is completely defeats the point of having an exception handler in your application.

You’d be better off just trying to update the default payment method and letting your exception handler handle exceptions:

public function register()
{
    $this->renderable(WhateverStripeException::class, function () {
        return redirect()->back()->withInput()->with('error', __('Error updating payment method.'));
    });
}

Your controller should only be interested in performing business logic; not error handling. Once the payment is updated, you could dispatch an event that has a listener to send whatever email. Again, your controller shouldn’t be concerned with side effects.

public function storePayment(Request $request)
{
    $user = $request->user();

    $user->updateDefaultPaymentMethod($request->input('setup_method'));

    DefaultPaymentMethodUpdated::dispatch($user);

    // Return successful response
}

Alternatively, you could just not dispatch an event at all given Stripe will send you a webhook if the customer's default payment method is updated, and you could instead send the email off the back of that webhook event.

axeloz's avatar

@martinbean yes you are right, sorry, Feature Testing. For the rest, I'm using Exceptions in Controllers to handle multiple possible errors situations. I see what you mean with the EventListeners but I can't create a Listener for every action that is made on a store method. Plus, the way I use exceptions allows me to hide the raw error to the customer but still return a proper and not-too-verbose error (without loosing the form content for instance)

If I were using a reactive app (AJAX calls), I would return 500 errors on this kind of call and my question would be solved with a assertOk.

Thanks

martinbean's avatar

Plus, the way I use exceptions allows me to hide the raw error to the customer but still return a proper and not-too-verbose error (without loosing the form content for instance)

@axeloz But that’s what your exception handler is for. Handling exceptions, and optionally converting them to your own HTTP responses.

axeloz's avatar

Hello @martinbean yes thanks, I did some testing and you are right, found a way. Thanks

1 like
SilenceBringer's avatar

@axeloz as far as you catch the Exception in your code - it will not be returned to the enduser. Check the error messsage instead

you can ctach different types of Exceptions and returns different messages

try {

} catch (EmailNotSentException $e) { // specific exception - name is just as example
			return back()
				->withInput()
				->with('error', 'Email sending failed')
			;
} catch (Exception $e) { // all other exceptions
			return back()
				->withInput()
				->with('error', 'We could not record your payment')
			;
}

or return details like

		catch (Exception $e) {
			report($e);
			return back()
				->withInput()
				->with('error', 'We could not record your payment')
				->with('error'Details, $e->getMessage())				
			;
		}
axeloz's avatar

@SilenceBringer thanks for your reply. This is what I'm doing actually. I figured a way not too dirty :

			return back()
				->withInput()
				->with('error', 'Something happened')
				->header('Exception-Error', $e->getMessage())

and in my feature tests:

$response->assertHeaderMissing('Exception-Error');

Not perfect but ...

Please or to participate in this conversation.