Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Mocking an interface restricting return type of a parent interface #1459

@GromNaN

Description

@GromNaN

Mockery Version

1.6.x

PHP Version

PHP 8.3

Issue Description

When I try to mock an interface that extends an other interface with more restrictive return type, the less restrictive return type from the parent interface is used to generate the method.
The generated method has the wrong return type, which is not permitted by PHP.

Steps to Reproduce

Create an interface that extends the standard Iterator and override the current or key method with a more restrictive return type:

/** @implements \Iterator<string, bool> */
interface RestrictReturnType extends \Iterator
{
    public function current(): ?bool; // Parent returns "mixed"
    public function key(): ?string;  // Parent returns "mixed"
}

Try to mock it:

\mock(RestrictReturnType::class);

Expected Behavior

This interface should be mockable.

Actual Behavior

Fatal Error, the generated code has the return type of Iterator::current() instead of RestrictReturnType::current().

public function current(): mixed{
$argc = func_num_args();
$argv = func_get_args();
$ret = $this->_mockery_handleMethodCall(__FUNCTION__, $argv);
return $ret;
}

    public function key(): mixed{
$argc = func_num_args();
$argv = func_get_args();
$ret = $this->_mockery_handleMethodCall(__FUNCTION__, $argv);
return $ret;
}

Exception or Error

PHP Fatal Error:

Declaration of Mockery_0_Iterator_RestrictReturnType::current(): mixed must be compatible with RestrictReturnType::current(): bool

Additional Information

In MockConfiguration::getAllMethods(), if I call $classes = array_reverse($classes); before iterating, that fixes the bug. But this is a poor fix that only acts on which method overrides the other. We need more intelligence to find which return type wins.

protected function getAllMethods()
{
if ($this->allMethods) {
return $this->allMethods;
}
$classes = $this->getTargetInterfaces();
if ($this->getTargetClass()) {
$classes[] = $this->getTargetClass();
}
$methods = [];
foreach ($classes as $class) {
$methods = array_merge($methods, $class->getMethods());
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageneeds to be triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions