Skip to content

[CodeQuality] Add AddIntersectionParamToMockObjectParamRector - #755

Merged
TomasVotruba merged 1 commit into
mainfrom
add-intersection-param-to-mock-object-param
Aug 1, 2026
Merged

[CodeQuality] Add AddIntersectionParamToMockObjectParamRector#755
TomasVotruba merged 1 commit into
mainfrom
add-intersection-param-to-mock-object-param

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

Private test methods that take a mock end up with a bare MockObject param type — either handwritten, or produced by ExpectsParamToMockObjectRector. The mocked class is then lost for static analysis, which is what forces @phpstan-ignore-next-line on every call on that param.

This rule puts the class back into an intersection @param, resolved from the mock actually passed at the call sites in the same class.

 final class SomeTest extends TestCase
 {
     public function testBuildForm(): void
     {
         $builder = $this->createMock(FormBuilderInterface::class);
         $eventListener = $this->getEventListenerFromBuildForm($type, $builder);
     }

+    /**
+     * @param \FormBuilderInterface&\PHPUnit\Framework\MockObject\MockObject $builder
+     */
     private function getEventListenerFromBuildForm(SomeType $type, MockObject $builder): callable
     {
         $builder->expects($this->once())
             ->method('addEventListener');
     }
 }

How the type is resolved

Only private methods are handled, as those can only be called from the very same class, so all call sites are visible.

The mocked class comes from the $this->createMock(Some::class) / self::createMock(Some::class) assignment of the passed variable, or from a createMock() call passed inline as an arg.

Skipped when the type would be a guess:

  • call sites disagree on the mocked class
  • no call site at all
  • the variable is re-assigned, so its type is not certain
  • createStub() source, that is a Stub, not a MockObject
  • a @param tag for that param already exists, to not contradict it
  • non-test class, public/protected method, variadic param

Sets

Registered in the composer-based set next to ExpectsParamToMockObjectRector, bonded to phpunit/phpunit >=11.0 to match its stub/mock siblings, and in the phpunit-mock-to-stub set.

Note on the intersection order: this prints \Some&\MockObject, while the existing AddIntersectionVarToMockObjectPropertyRector prints \MockObject&\Some. Happy to flip either one for consistency.

Add a MockObject intersection @param docblock to a private test method
param typed as a bare MockObject, based on the mock passed at the call
sites within the same class.

This complements ExpectsParamToMockObjectRector, that narrows such a
param to a native MockObject type and thereby loses the mocked class for
static analysis.

The mocked class is resolved from the $this->createMock(Some::class)
call the passed variable is assigned from. The docblock is only added
when every call site passes a mock of the very same class, so the type
is never guessed.

Register it in the composer-based set, bonded to phpunit/phpunit >=11.0
to match its stub/mock siblings, and in the mock-to-stub set.
@TomasVotruba
TomasVotruba merged commit 0f29226 into main Aug 1, 2026
7 checks passed
@TomasVotruba
TomasVotruba deleted the add-intersection-param-to-mock-object-param branch August 1, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant