Skip to content
LaravelBlog

laravel/framework: [13.x] Test Improvements: Annotating Intentionally Expectation-Free Mocks

LaravelBlogBot
LaravelBlogBot

Contributed by crynobone

Part of the framework v13.35.0 release

Pull request #61790 is pure test-suite housekeeping on the 13.x branch. It applies PHPUnit's #[AllowMockObjectsWithoutExpectations] attribute to dozens of framework unit tests that deliberately create mocks without ever configuring expectations on them, and it adjusts one brittle assertion in the Eloquent factory tests to use null-safe property access. There are no changes under src/, so the framework's runtime behavior, public API, and configuration are untouched. If you're upgrading from v13.34.0, this is a zero-impact change for your application — but it's worth understanding, both as a signal about PHPUnit's direction and as a pattern you may want to adopt in your own test suites.

What Changed

The pull request touches roughly eighteen test files spanning Auth, Cache, Database (connections, Eloquent, migrations, schema state), Http, Mail, Queue, Redis, Translation, and Validation. In each case the edit is mechanical: import PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations and place the attribute on the affected test methods.

Typical examples include testLogoutDoesNotSetRememberTokenIfNotPreviouslySet in AuthGuardTest, the transaction event tests in DatabaseConnectionTest (which use stubbed PDO objects purely to satisfy the connection's constructor), the queued-mailable tests in MailableQueuedTest (which rely on a queue fake rather than mock expectations), the size and comparison validation tests in ValidationValidatorTest, and the cache file store tests that use Mockery::mock(Filesystem::class)->shouldIgnoreMissing() as an inert filesystem stand-in. The common thread: the mock exists to be passed in, not to be verified.

The one non-annotation change lives in DatabaseEloquentFactoryTest, where a single assertion was rewritten (more on that below).

Why It Matters

Recent PHPUnit releases (12.1 and later) treat a mock object that is created but never given expectations — and never verified — as a test runner warning. That's a sensible default for application code: a mock you never configure and never assert against is often a leftover or a mistake. But framework tests legitimately use mocks in a different way. A stubbed PDO handed to a DatabaseConnection so you can assert on transaction events, or a Filesystem mock with shouldIgnoreMissing() used only to exercise permission handling, produces no meaningful expectations to declare. Forcing expectations onto them would add noise without adding coverage.

The #[AllowMockObjectsWithoutExpectations] attribute is PHPUnit's official opt-out for exactly this situation. By annotating these tests, the framework keeps its CI output clean of spurious warnings, so genuine warnings — deprecations, skipped assertions, real regressions — don't drown in known-and-accepted ones. It also documents intent at the test level: anyone reading the suite knows the expectation-free mock is deliberate.

The One Assertion Tweak

In test_can_disable_relationships_explicitly_by_both_attribute_name_and_method, the old assertion read:

$this->assertNull($comment->commentable->id);

When a relationship is disabled explicitly, $comment->commentable is null, so reading ->id off it triggers PHP's "attempt to read property on null" warning — which modern PHPUnit escalates. The assertion passed only because PHP historically coerced that warning into a null result. The PR replaces it with an unambiguous pair:

$this->assertNull($comment->commentable);
$this->assertNull($comment->commentable?->id);

This is a strictly better test: it asserts the relationship itself is null, then uses the nullsafe operator to express the follow-up check without relying on warning-suppression behavior.

Upgrade Impact

For applications upgrading from v13.34.0: none. The framework's tests/ directory isn't shipped to your app, and no src/ code changed in this diff. There are no new methods, deprecations, or behavioral shifts to account for, and nothing to change in your composer.json or configuration.

If you contribute to the framework or run its test suite directly, you'll want PHPUnit 12.1+ so the attribute class exists. Notably, the annotation is harmless even on older PHPUnit versions: PHP use statements don't trigger autoloading, and unknown attributes are simply ignored via reflection, so the change is safe to merge regardless of your test tooling version.

What This Looks Like in Your Own Tests

If you maintain a package or a large test suite that will migrate to PHPUnit 12.1+, the same pattern applies. Consider a test where a mock only satisfies a constructor:

use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
use PHPUnit\Framework\TestCase;

class RegisterUserServiceTest extends TestCase
{
    #[AllowMockObjectsWithoutExpectations]
    public function testItHashesPasswordsBeforePersisting(): void
    {
        // Inert dependency: the assertion is on the return value,
        // not on any interaction with the repository.
        $repository = $this->createMock(UserRepository::class);

        $service = new RegisterUserService($repository);

        $this->assertNotSame('secret', $service->hashPassword('secret'));
    }
}

Here the missing expectations are intentional, and the attribute tells PHPUnit so. The attribute can also be applied at the class level to cover every test in a suite at once, which is a good fit for test classes that are uniformly stub-driven.

Takeaways

  • No runtime impact: only the framework's test suite changed; upgrading from v13.34.0 requires no action.
  • PHPUnit alignment: the new #[AllowMockObjectsWithoutExpectations] attribute (PHPUnit 12.1+) silences warnings for mocks intentionally used without expectations.
  • One assertion hardened: the Eloquent factory test now asserts commentable is null directly and uses the nullsafe ?->id, instead of relying on a nullable property-read warning.
  • Pattern worth copying: package maintainers moving to PHPUnit 12.1+ can annotate their own stub-driven tests the same way to keep CI output clean.

Sources

More from this release

Related Articles