Testing Exceptions and Errors

expectException() declares, before the code runs, that the rest of the test must throw; expectExceptionMessage() and expectExceptionCode() narrow it. To inspect the exception's own properties, such as OutOfStockException::$available, catch it yourself and call $this->fail() after the line that should have thrown.

tests/Unit/OrderStockTest.php, an exception and a deprecation as expected outcomesPHP
<?php
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\TestCase;
use Shop\{Money, Order, OutOfStockException, Product};
final class OrderStockTest extends TestCase {
  public function testOrderingMoreThanTheStockThrows(): void {
    $this->expectException(OutOfStockException::class);
    $this->expectExceptionMessage('BK-UX-01: only 7 left');
    new Order()->add(new Product('BK-UX-01', 'Calm Interfaces', new Money(3400), 7), 8);
  }
  #[IgnoreDeprecations]
  public function testTheFloatFactoryIsDeprecated(): void {
    $this->expectUserDeprecationMessage(
      'Method Shop\Money::fromFloat() is deprecated since 2.0, use fromDecimal()');
    $this->assertSame(1999, Money::fromFloat(19.99)->cents);
  }
}

Since PHPUnit 10 79,198 , PHP warnings, notices and deprecations are no longer turned into exceptions: the test keeps running, the report lists them as issues, and the failOn* flags decide the outcome. fromFloat() carries PHP 8.4's #[\Deprecated] attribute, which raises E_USER_DEPRECATED; expectUserDeprecationMessage() asserts it, and #[IgnoreDeprecations] keeps the expected notice out of the report. Both tests pass.