phpspec / prophecy

Highly opinionated mocking framework for PHP 5.3+
MIT License
8.53k stars 242 forks source link

mocking protected methods #611

Closed bshaffer closed 1 year ago

bshaffer commented 1 year ago

I've noticed that mocking protected methods has no effect. The methods are called, but they return empty values. Example:

class Foo
{
    public function __construct(private string $bar) {}

    public function compareFoo(Foo $otherFoo): bool
    {
        return $this->bar === $otherFoo->getBar();
    }

    protected function getBar()
    {
        return $this->bar;
    }
}

In a test, something like this cannot be done (it will be false):

$foo = new Foo('someval');
$foo2 = $this->prophesize(Foo::class);
$foo2->getBar()->willReturn('someval');
$this->assertTrue($foo->compareFoo($foo2->reveal()));

Although this may fall into the "you shouldnt mock protected methods", it seems like a valid use-case to mock to me (because in the way it's being used here, it's a "public" method when being called from the same class).

Is there something I'm missing? Is there a way to mock a protected method like this, or is it WAI?

stof commented 1 year ago

Prophecy does not support partial mocking. This is an intended choice.

The issue in your case is that you don't configure the behavior of the mock for compareFoo so the default behavior (returning a default value for the return type) is what happens.

stof commented 1 year ago

See this note at the end of the readme: https://github.com/phpspec/prophecy#can-i-call-the-original-methods-on-a-prophesized-class

bshaffer commented 1 year ago

@stof thanks for the reply, although I'm not sure what you're saying is accurate. I'm not trying to do partial mocking... compareFoo is only called on an instance of Foo, not on a mock object of Foo. The only method being called on the mock object is getBar.

stof commented 1 year ago

ah indeed. This is a case of an object calling a non-public method on a different instance of the same class. This is not supported by Prophecy.

Note that such pattern even allows reading private properties directly. So it is almost impossible to mock it.

bshaffer commented 1 year ago

@stof wow, I didn't realize it was possible to call private methods that way :/

Thanks for the info! I'll go ahead and close this (and avoid using that pattern)