Skip to content

Fix exception message assertions on PHPUnit 13.2 and later - #94

Merged
mbabker merged 1 commit into
SymfonyTest:masterfrom
hugo-goncalves-kununu:fix/phpunit-13.2-exception-message-constraint
Sep 15, 2026
Merged

mbabker merged 1 commit into
SymfonyTest:masterfrom
hugo-goncalves-kununu:fix/phpunit-13.2-exception-message-constraint

Conversation

@hugo-goncalves-kununu

@hugo-goncalves-kununu hugo-goncalves-kununu commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Every assertion that carries an expected message fails on PHPUnit >= 13.2, regardless of what the actual exception message is:

Failed asserting that exception message '' contains 'required_value'.

This affects assertConfigurationIsInvalid() and assertPartialConfigurationIsInvalid() whenever $expectedMessage is given, with or without $useRegExp. Calls without an expected message are unaffected.

This repository's own test suite is already red. On master with PHPUnit 13.3.4, 4 of 39 tests fail:

1) ConfigurationTestCaseTraitTest::it_can_assert_that_a_configuration_is_invalid
2) ConfigurationValuesAreInvalidConstraintTest::if_configuration_values_are_invalid_it_matches_when_exception_message_is_right
3) ConfigurationValuesAreInvalidConstraintTest::if_configuration_values_are_invalid_it_matches_when_exception_message_is_right_according_to_regexp
4) PartialConfigurationIntegrationTest::it_can_assert_that_a_configuration_is_invalid

CI has not caught it because it only runs on push and pull_request, and the last run was 2026-02-09, before PHPUnit 13.2.0 was released on 2026-06-05. The matrix does not pin a PHPUnit version, so the PHP 8.4 legs resolve to 13.3.x and will go red as soon as anything reruns.

Cause

ConfigurationValuesAreInvalidConstraint::evaluateException() passes the caught InvalidConfigurationException object to the PHPUnit constraint. Up to PHPUnit 13.1 that worked by accident, because the constraints string-cast whatever they were given and Throwable::__toString() contains the message:

// PHPUnit <= 13.1
return str_contains((string) $other, $this->expectedMessage);

PHPUnit 13.2.0 (sebastianbergmann/phpunit#6559, "Improved API for exception message expectations") tightened ExceptionMessageIsOrContains, ExceptionMessageIs and ExceptionMessageMatchesRegularExpression to accept only the message string:

// PHPUnit >= 13.2
if (!is_string($other)) {
    return false;
}

return str_contains($other, $this->expectedMessage);

PHPUnit itself now always feeds these constraints $exception->getMessage() (see PHPUnit\Framework\TestCase\ExceptionExpectation::verify()). The classes are marked @internal in PHPUnit, so this is not a BC break on their side.

Fix

Pass the message rather than the exception. One line, and it covers both the plain and the regexp paths since they share the call site.

Compatibility

Verified locally on PHP 8.4.25, running the full suite against each supported major:

PHPUnit Result
10.5.64 OK (40 tests, 66 assertions)
11.5.56 OK (40 tests, 66 assertions)
12.5.35 OK (40 tests, 66 assertions)
13.3.4 OK (40 tests, 66 assertions)

On 10.5 through 13.1 the constraints do str_contains((string) $other, ...), which is identical for a plain string, so nothing changes there. The change also repairs the $expectedMessage === '' branch, which could never match while an object was passed.

Test

Added the_failure_message_contains_the_actual_exception_message, which asserts that a mismatched expectation reports the real message. Reverting the one-line fix makes it fail with:

Failed asserting that 'Failed asserting that exception message '' contains '...'.' contains "required_value".

The four pre-existing failures listed above also turn green.

Relation to #91

#91 removes the unreachable PHPUnit 9.6 (ExceptionMessage) and PHPUnit 10.0.0-10.0.14 (MessageIsOrContains) fallbacks from createPhpUnitConstraint(), which are dead code given composer.json requires phpunit/phpunit: ^10.5 || ^11.0 || ^12.0 || ^13.0. That cleanup is worth having and I have deliberately not duplicated it here.

Note that #91 does not fix this bug: it keeps passing $exception to evaluate() in both branches, so the suite stays red on PHPUnit >= 13.2. The two changes touch the same lines and will conflict textually, so whichever lands second needs a trivial rebase:

🤖 Generated with Claude Code

ConfigurationValuesAreInvalidConstraint passed the caught
InvalidConfigurationException object to PHPUnit's exception message
constraints. Up to PHPUnit 13.1 those constraints string-cast whatever
they were given, so passing the exception happened to work, because
Throwable::__toString() contains the message.

PHPUnit 13.2 (sebastianbergmann/phpunit#6559) tightened
ExceptionMessageIsOrContains, ExceptionMessageIs and
ExceptionMessageMatchesRegularExpression to accept only the message
string, so every assertion carrying an expected message now fails with
"Failed asserting that exception message '' contains '...'", whatever
the actual message is. Four tests in this suite are affected.

Pass the message instead, which is what PHPUnit itself feeds these
constraints. This behaves identically on PHPUnit 10.5 through 13.1,
where the constraints string-cast their input, and it also repairs the
empty expected message branch, which could never match while an object
was passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mbabker
mbabker merged commit c3daa66 into SymfonyTest:master Sep 15, 2026
14 checks passed
@hugo-goncalves-kununu

Copy link
Copy Markdown
Contributor Author

@mbabker thank you! Can you please create a new tag/release since this has been merged?

@mbabker

mbabker commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Consider it done, 6.2.1 released.

@hugo-goncalves-kununu

Copy link
Copy Markdown
Contributor Author

Thank you very much @mbabker !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants