codeception/assert-throws
Assert that a callable throws the expected exception without try/catch boilerplate. codeception/assert-throws adds assertThrows and assertDoesNotThrow helpers for Codeception and PHPUnit tests, with clear failure messages and optional checks on exception class and message.
Classify it as developer-productivity spend, not a roadmap item. This is a dev-only test dependency with zero production footprint — no service provider, no config, no runtime surface in a Laravel app. Approve it on adoption cost and reversibility, not on feature value: roughly one developer-day to install and wire the trait into a shared base TestCase, and reversal is deleting one require-dev line and a use statement.
The real build-vs-buy question is "who maintains PHPUnit compatibility." Reimplementing assertThrows() is ~30 lines of try/catch in a base test class — any engineer can do it in an afternoon. What you are actually buying is someone else tracking PHPUnit majors; the release history (1.3.0 → 1.4.0 → 1.5.0 → 1.6.0) shows PHPUnit 10, 11, 12, and 13 support landing as point releases. An in-house trait silently rots at every PHPUnit upgrade. Frame the decision that way and it survives the "we could just build it" objection.
Unlocks error-path coverage as a realistic quality gate. PHPUnit's native expectException() halts the test at the first throw, so each test can validate exactly one failure. The non-halting assertThrows() lets a single test assert several invalid inputs and report all failures. That is what turns "every endpoint rejects malformed input with the documented exception" from a per-test tax into something you can actually enforce in CI. It supports any initiative framed as contract testing, API error standardization, or reducing incidents from unhandled exceptions.
Concrete Laravel use cases to attach it to:
ValidationException) across many payloads in one test method.try/catch currently hides intent.assertDoesNotThrow regression tests on the happy path — a genuine gap in most suites.Baseline these signals before adopting, so you can claim impact later: count of expectException and hand-rolled try/catch occurrences in tests, count of tests with exactly one exception assertion, error-path coverage on high-risk modules (auth, payments, validation), and test-suite flake rate. Don't promise a coverage percentage you can't measure.
Due-diligence flags to resolve before sign-off — not blockers: the repo description still says "PHPUnit 6+" while 1.1.0 dropped it; secondary write-ups disagree on the PHP floor (7.4 vs 8.0). Verify the compatibility matrix against composer.json yourself. Maturity signals are thin — 19 stars, 0 dependents — but it ships under the Codeception org, so abandonment risk reads as "org stops caring" rather than "single hobbyist disappears." Cheap and forkable, not strategic.
Adopt if:
try/catch in tests or single-assertion expectException() methods — the pain is observable today, not hypothetical.Look elsewhere or skip if:
->throws() covers the main use case idiomatically; layering a third-party trait on top is friction with no payoff. This is the single most likely reason to say no.expectException() is genuinely sufficient. Value scales with the number of error-path tests; below that threshold it's a dependency for its own sake.getPrevious() chains, regex message patterns, stack inspection. The package is deliberately thin; you'll wrap it anyway, at which point owning the wrapper is the honest answer.Sequencing recommendation: pilot on the highest-risk module (auth, payments, validation) behind a shared base TestCase that uses the trait. Convert 3–5 high-signal tests — form-request validation, policy denials, domain invariant guards — and leave single-exception tests on native expectException(); that is not technical debt. Codify the split in CONTRIBUTING.md so reviewers have one rule. If the pilot doesn't visibly improve failure diagnostics after one sprint, drop it — the sunk cost is one day.
For executives — decisions-and-risk framing: "We have a gap in how we test failure cases. Today, when we test that something should fail — a rejected payment, a blocked permission, an invalid form — the test stops at the first failure, so we can only check one bad input per test. As a result our tests cover the happy path far better than the failure paths. This is a small, free, dev-only PHP library that lets one test check many failure cases and keep going, so we get clearer diagnostics and better coverage of exactly the scenarios that cause production incidents. It never ships to production, it's one developer-day to pilot, and it's fully reversible. I'm not asking for budget — I'm asking for a one-sprint pilot on the auth and validation modules."
For engineering — the honest version, including the "just build it" objection: "Problem: expectException() halts the test, so our exception tests are one-and-done, and half of them are hand-rolled try/catch noise that hides intent.
// today: one bad input per test, or a try/catch that obscures intent
$this->expectException(ValidationException::class);
$this->post('/register', ['email' => 'nope']);
// with the package: many cases, all reported, test keeps running
$this->assertThrows(ValidationException::class, fn () => $this->post('/register', ['email' => 'nope']));
$this->assertThrows(ValidationException::class, fn () => $this->post('/register', ['email' => '']));
$this->assertDoesNotThrow(fn () => $this->post('/register', ['email' => '[email protected]']));
Yes, we could write this ourselves in 30 lines — the catch is that our version breaks on every PHPUnit major and nobody will own fixing it. The only thing we're really buying is someone else shipping PHPUnit 10/11/12 support, which the release history shows they do.
Ask: approve require-dev, let me add the trait to the base TestCase, and pilot it on auth + validation, reporting back after one sprint. Constraints: dev-only, never in the production autoload path, no runtime surface. Known unknowns: low adoption (19 stars), thin docs, and the repo's stated PHPUnit/PHP compatibility contradicts its own release notes — I'll verify the matrix against composer.json before merging anything."
How can I help you explore Laravel packages today?