- How do I install codeception/assert-throws in a Laravel project?
- Install it as a dev dependency with composer require --dev codeception/assert-throws. Then add the AssertThrowsTrait to your project's base TestCase, which typically extends Illuminate\Foundation\Testing\TestCase. Every feature and unit test that inherits from that base class gets the four assertion methods without any per-file boilerplate. There is no service provider to register and no config to publish.
- Does codeception/assert-throws work with Laravel's default PHPUnit test suite?
- Yes. The package is framework agnostic and ships only a trait, so it runs wherever PHPUnit or Codeception runs, including Laravel's default phpunit runner. Laravel's TestCase is a PHPUnit test case, so adding the trait to your base class is enough. You do not need to install Codeception itself to use it.
- What is the difference between assertThrows and PHPUnit's built-in expectException?
- PHPUnit's expectException registers an expectation and then halts the test when the exception fires, so the rest of the test body never runs. assertThrows wraps the callable in a try/catch and asserts on the result, letting the test continue afterwards. That means you can exercise several exception paths in a single test and keep asserting on state after each one. If you only need one exception per test, native expectException is perfectly fine and is not technical debt.
- How do I assert an exception message with assertThrowsWithMessage?
- Use assertThrowsWithMessage instead of assertThrows and pass the expected message string alongside the exception class and the callable under test. The trait also provides assertDoesNotThrow and assertDoesNotThrowWithMessage for the inverse case. Check the README for the exact argument order in the version you pin, since it is a small package and the signature is the whole API surface.
- Can I test Laravel ValidationException, ModelNotFoundException, or AuthorizationException with assertThrows?
- Yes, any throwable class works, and Laravel's framework exceptions are the highest-signal targets. Form request validation failures, policy denials, and route model binding misses are good candidates because a single test can cover several exception paths. Keep an exception taxonomy cheat sheet so reviewers know which class each failure mode should raise. Use it where multiple paths per test genuinely helps, not as a blanket replacement.
- Does codeception/assert-throws add any production dependencies or service providers?
- No. It is a test-layer-only utility that ships a trait with four assertion methods and nothing else: no service provider, no facade, no config, no runtime dependency. Because it is installed with require --dev, it has zero production footprint in a Laravel application. Architecturally it sits beside Laravel's testing helpers rather than inside them, which is the right place for it.
- Which PHP, PHPUnit, and Laravel versions does codeception/assert-throws support?
- It supports PHPUnit 6 and later, with releases 1.3.0, 1.4.0, and 1.5.0 adding PHPUnit 10, 11, and 12 support respectively. Laravel 8+ on PHP 7.4+ works because Laravel's test case is a PHPUnit test case, and Codeception 4+ is supported if your suite uses it. Pin to ^1.5 in require-dev and let your Composer lockfile govern upgrades.
- Is there an assertDoesNotThrow method for code that should not throw?
- Yes, the trait provides assertDoesNotThrow and assertDoesNotThrowWithMessage alongside the two throwing assertions. They verify that a callable completes without raising an exception, optionally checking that no exception with a given message was raised. These are useful for guarding happy paths right next to the failure paths in the same test. They are a convenience wrapper, not a substitute for proper assertions on the returned value.
- Is codeception/assert-throws actively maintained, and what happens if it gets abandoned?
- Treat it as a cheap, forkable convenience rather than a strategic dependency. It has a small maintainer pool and few dependents, and the code is only a few dozen lines wrapping try/catch, so the exit cost is a fork or an in-house trait rather than a migration. Decide your fork threshold up front, and if upstream goes quiet you can vendor the trait in an afternoon. Pinning to ^1.5 keeps upgrades predictable in the meantime.
- Should my team standardize on assertThrows instead of expectException everywhere?
- No, adopt it selectively rather than as a big-bang rewrite. Convert a bounded, high-signal slice such as form request validation, authorization denials, and domain invariant guards, and leave single-exception tests on native expectException. Write the rule down in CONTRIBUTING.md so reviewers have one consistent mental model of what a failing exception test looks like. Also note that non-halting assertions can cascade into noisy secondary failures, so the convention matters as much as the package.