Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpunit Assert Throws Laravel Package

jchook/phpunit-assert-throws

A PHPUnit assertion helper for testing that code throws exceptions. It adds assertThrows to verify exception types and messages in a single call, keeping tests compact and readable while confirming the expected error is actually raised.

Deep Wiki
Context7

Getting Started

Install as a dev dependency:

composer require --dev jchook/phpunit-assert-throws

Then opt into the assertions by pulling the trait into a test class (or, better, into your project's base TestCase):

use Jchook\AssertThrows\AssertThrows;
use PHPUnit\Framework\TestCase;

class InvoiceTest extends TestCase
{
    use AssertThrows;
}

The first thing to read is the trait itself (src/AssertThrows.php or similar in the vendor dir) — it is a handful of methods, so the source is the documentation. The canonical first use case is asserting on an exception's contents, which PHPUnit's built-in expectException cannot do:

public function testRejectsNegativeAmount(): void
{
    $exception = $this->assertThrows(
        fn () => (new Invoice)->addLine(-1),
        InvalidArgumentException::class
    );

    $this->assertSame('Amount must be positive', $exception->getMessage());
}

If you only need "this throws class X", skip the package entirely and use native expectException. Reach for this trait the moment you want the exception object back to inspect and keep going.

Implementation Patterns

Inspect returned exceptions instead of chaining expectException. The key ergonomic win is that the assertion returns the caught Throwable, so you can assert message, code, getPrevious(), or custom accessor methods in the same test:

$e = $this->assertThrows(fn () => $gateway->charge($card), PaymentException::class);

$this->assertSame(PaymentException::DECLINED, $e->getCode());
$this->assertInstanceOf(NetworkException::class, $e->getPrevious());

Test multiple throw sites in one method. Because the trait does not install PHPUnit's global exception handler expectation, you are not locked into "one expected exception per test." For a validator with several guard clauses this reads far better than splitting into many tiny tests:

$this->assertThrows(fn () => $validator->validate([]), ValidationException::class);
$this->assertThrows(fn () => $validator->validate(['email' => 'nope']), ValidationException::class);

Drive it from data providers. Wrap the callable in the provider so each dataset describes its own failure:

#[DataProvider('badInputs')]
public function testValidation(callable $input, string $message): void
{
    $e = $this->assertThrows(fn () => (new Validator)->run($input), ValidationException::class);
    $this->assertStringContainsString($message, $e->getMessage());
}

Laravel / Symfony integration. In a framework test suite that already provides a base TestCase, add the trait to that base class once and it is available everywhere. In Laravel's Tests\TestCase, $this->assertThrows(...) then works alongside $this->postJson(...) etc. — just be aware it shadows nothing in Laravel's InteractsWithExceptionHandling because the names differ.

Wrap it as a project helper. If you standardize on it, expose small conveniences in your base test class (e.g. assertThrowsWithMessage($callback, $class, $needle)) so tests express intent rather than asserting message substrings by hand at every call site.

Gotchas and Tips

  • Version compatibility is the big one. This is an 11-star, low-maintenance package; the trait was written against older PHPUnit internals. Verify it installs and runs against your PHPUnit major version (8/9/10/11) before standardizing on it — method visibility and TestCase internals changed across majors. Pin PHPUnit if necessary or vendor a copy of the trait.

  • Trait must be present at the call site. Unlike a global bootstrap, $this->assertThrows() only exists inside classes (or their parents) that use the trait. A missing trait gives a confusing "Call to undefined method" error rather than a helpful message.

  • Wrong-type exceptions bubble out. If the callback throws something other than the class you asserted, it typically escapes the assertion and fails the test as an uncaught exception. Read the stack trace carefully — it usually means the code failed differently than you expected, not that the assertion is broken.

  • A callback that throws nothing fails the assertion (good), but a callback that catches internally and returns the exception instead of throwing will also fail. Make sure your subject actually throws.

  • Don't mix with expectException in the same test. expectException sets a global expectation, while this trait catches the throwable itself; combining them leads to "exception was thrown but no exception expected"-style confusion. Pick one style per test.

  • Message assertions: if the trait offers a message helper, remember it likely uses substring matching, not exact equality. Prefer asserting on the returned exception with assertSame when the wording matters, so whitespace changes fail loudly.

  • Debugging: var_dump($e) on the returned exception inside the test is the fastest way to see the actual class hierarchy (custom exception subclasses, wrapped $previous) — much faster than reading PHPUnit's truncated failure output.

  • Extension point: the traits are small and self-contained; the pragmatic long-term move for a critical suite is to copy the trait into your own test-support namespace. That frees you from the package's release cadence and lets you adjust to future PHPUnit majors yourself.

  • When not to use it: simple "assert it throws" tests are cleaner and more future-proof with native expectException, and return-value tests should stay as plain assertions. Reserve this package for cases where you genuinely need the exception object to make further assertions in the same test.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core