sebastianbergmann/phpunit · error · ErrorException

E_USER_ERROR was triggered

Error message

E_USER_ERROR was triggered

What it means

PHPUnit installs its own error handler while a test runs. When code executed inside that test calls trigger_error() with E_USER_ERROR severity, the handler emits a Test\ErrorTriggered event and then throws PHPUnit\Runner\ErrorException('E_USER_ERROR was triggered') so the test is marked as errored. Lower severities (notice, warning, deprecation) are only recorded; E_USER_ERROR is the only user-raised level converted into an exception, because PHP treats it as an abort-level error.

Solutions

  1. Fix the code under test to throw an exception instead of calling trigger_error(..., E_USER_ERROR).
  2. If the error is expected in this test, declare it: $this->expectException(\PHPUnit\Runner\ErrorException::class); before the triggering call.
  3. If the call must not abort the test, wrap it in a try/catch for \PHPUnit\Runner\ErrorException inside the test and assert on state afterwards.
  4. Downgrade the severity to E_USER_WARNING or E_USER_DEPRECATED when abort semantics are not required; PHPUnit then records the issue instead of throwing.

Example fix

// before
public function testLegacyAbort(): void
{
    LegacyLogger::fail('disk full'); // calls trigger_error(..., E_USER_ERROR)
    self::assertTrue(true);
}

// after
public function testLegacyAbort(): void
{
    $this->expectException(\PHPUnit\Runner\ErrorException::class);
    LegacyLogger::fail('disk full');
}
Defensive patterns

Strategy: try-catch

Try / catch

// inside a PHPUnit test
try {
    LegacyCode::run(); // may call trigger_error(..., E_USER_ERROR)
    self::assertTrue($expectedState);
} catch (\PHPUnit\Runner\ErrorException $e) {
    // reached only when E_USER_ERROR was raised during the call
    self::assertSame('E_USER_ERROR was triggered', $e->getMessage());
}

Prevention

When it happens

Trigger: Test code, or production code the test invokes, executes trigger_error($message, E_USER_ERROR) (E_USER_ERROR is also the default severity of trigger_error(), so a bare trigger_error('msg') in PHP 8 hits this path). Typical sources: legacy libraries that signal fatal conditions via user errors instead of exceptions, or old hand-rolled assertion/abort helpers.

Common situations: Writing tests around legacy PHP code that used trigger_error(E_USER_ERROR) instead of throwing; a dependency raising E_USER_ERROR deep inside a code path under test; porting old test suites whose helpers abort via user errors.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/c6764a2f2a8c4816. Report an issue: GitHub.

Appendix: source

Thrown at src/Runner/ErrorHandler.php:310

                    $ignoredByBaseline,
                    $ignoredByTest,
                    $this->deprecationIgnoredByFilter($errorString, $errorFile, $errorLine, $trigger),
                    $trigger,
                    $this->stackTrace($errorFile, $errorLine),
                );

                break;

            case E_USER_ERROR:
                Event\Facade::emitter()->testTriggeredError(
                    $test,
                    $errorString,
                    $errorFile,
                    $errorLine,
                    $suppressed,
                );

                throw new ErrorException('E_USER_ERROR was triggered');

                /**
                 * No other error type that can be handled by a user-defined
                 * error handler is raised by PHP 8.
                 */
                // @codeCoverageIgnoreStart
            default:
                return $handledByPreviousErrorHandler;
                // @codeCoverageIgnoreEnd
        }

        return $handledByPreviousErrorHandler;
    }

    public function handleNonTestCaseIssue(int $errorNumber, string $errorString, string $errorFile, int $errorLine): true
    {
        /**
         * A previously registered error handler may delegate an error that is being

View on GitHub (pinned to f123cdb2a2)