Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: what the reason for existing SingleMockPropertyTypeRector? #419

Closed
andrew-demb opened this issue Dec 17, 2024 · 4 comments
Closed

Comments

@andrew-demb
Copy link

https://getrector.com/rule-detail/single-mock-property-type-rector

Could you tell me what the reason is for introducing such a rule?

As I see in the code of the rule - it will refactor this:

private MockObject&SomeObject $foobar;

To this:

private MockObject $foobar;

Using the intersection type for such a use case is super clear, in my opinion, and this should not be refactored to mention only MockObject.


Introduced in #401

@TomasVotruba
Copy link
Member

Thanks for this question. The reason is the main identity in code.

See https://tomasvotruba.com/blog/5-ways-to-extract-value-from-overmocked-tests section 4

@andrew-demb
Copy link
Author

By using intersection types, I can help IDE / static analysis with type inference. When passing this property to a function that requires the original type, it ensures safety and correctness.

Here is a full example:

<?php

declare(strict_types=1);

namespace GitHubExample\Tests;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

final class ResultStatusDeprecatedRectorTest extends TestCase
{
    // rector will change this to `private MockObject $foobar;`
    private SomeObject&MockObject $foobar;

    protected function setUp(): void
    {
        $b = $this->getMockBuilder(SomeObject::class);
        $b
            ->enableOriginalConstructor()
            ->onlyMethods(['someMethod']);

        $this->foobar = $b->getMock();

        parent::setUp();
    }

    public function testSuccess(): void
    {
        $this->foobar->expects($this->once())->method('someMethod')->willReturn(59);

        $toTest = new class($this->foobar) {
            public function __construct(private readonly SomeObject $inner)
            {
            }

            public function someFacadeMethod(): int
            {
                $this->inner->changeState();

                return $this->inner->someMethod();
            }
        };

        $this->assertSame(59, $toTest->someFacadeMethod());
        $this->assertTrue($this->foobar->getState());
    }
}

class SomeObject
{
    private bool $state = false;

    public function changeState(): void
    {
        $this->state = true;
    }

    public function getState(): bool
    {
        return $this->state;
    }

    public function someMethod(): int
    {
        return 42;
    }
}

I believe the recommended code quality rule should enforce the use of intersection types rather than union types or just a MockObject.

What is your opinion on changing the recommended rule behavior for this use case?

@TomasVotruba
Copy link
Member

If both types are allowed, mock and real object can be assigned. Mock overridden to object, then overridden to object. What a mess. This was code smell in few projects, this rule will prevent it.

Also, IDE autocompletes real methods on mocked objects later in the code, giving a false positive hint that its a real object. This rule prevents it.

Feel free to exclude it via skip(), if it doesn't feel a good fit for you 👍

@andrew-demb
Copy link
Author

Ok, thanks.


I meant two separated methods (one for mock, one for real type).

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

No branches or pull requests

2 participants