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

Return Value Generator fails to correctly create test stub for method with static return type declaration when used recursively #5593

Closed
dkarlovi opened this issue Nov 30, 2023 · 5 comments
Assignees
Labels
feature/test-doubles Test Stubs and Mock Objects type/bug Something is broken version/10 Something affects PHPUnit 10

Comments

@dkarlovi
Copy link

Q A
PHPUnit version 10.4.2
PHP version 8.3.0
Installation Method Composer

Summary

Given this code

interface CacheItemInterface
{
    public function expiresAfter(int|\DateInterval|null $time): static;
}

interface CacheItemPoolInterface
{
    public function getItem(string $key): CacheItemInterface;
}

running

return $this->getMockBuilder(CacheItemPoolInterface::class)->getMock();

yields

PHPUnit\Framework\MockObject\RuntimeException: Return value for Psr\Cache\CacheItemInterface::expiresAfter() cannot be generated: PHPUnit\Framework\MockObject\ReturnValueGenerator::newInstanceOf(): Return value must be of type PHPUnit\Framework\MockObject\MockObject, TestStub_CacheItemInterface_b24f5332 returned

Changing static to self fixes it.

Current behavior

See above.

How to reproduce

See above.

Expected behavior

Mocking works.

@dkarlovi dkarlovi added type/bug Something is broken version/10 Something affects PHPUnit 10 labels Nov 30, 2023
@sebastianbergmann sebastianbergmann added the feature/test-doubles Test Stubs and Mock Objects label Dec 1, 2023
@sebastianbergmann
Copy link
Owner

Thank you for your report.

Please provide a minimal, self-contained, reproducing test case that shows the problem you are reporting.

Without such a minimal, self-contained, reproducing test case I will not be able to investigate this issue.

@sebastianbergmann sebastianbergmann added the status/waiting-for-feedback Waiting for feedback from original reporter label Dec 1, 2023
@dkarlovi
Copy link
Author

dkarlovi commented Dec 1, 2023

<?php

require __DIR__ .'/vendor/autoload.php';

interface CacheItemInterface
{
    public function expiresAfter(int|\DateInterval|null $time): static;
}

interface CacheItemPoolInterface
{
    public function getItem(string $key): CacheItemInterface;
}

class MockTestCase extends \PHPUnit\Framework\TestCase
{
    public function testMock(): void
    {
     	$p = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock();
        $i = $p->getItem('aa');
        $i->expiresAfter(1);
    }
}
$ vendor/bin/phpunit TestCase.php 
PHPUnit 10.4.2 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.13

E                                                                   1 / 1 (100%)

Time: 00:00.014, Memory: 4.00 MB

There was 1 error:

1) MockTestCase::testMock
PHPUnit\Framework\MockObject\RuntimeException: Return value for CacheItemInterface::expiresAfter() cannot be generated: PHPUnit\Framework\MockObject\ReturnValueGenerator::newInstanceOf(): Return value must be of type PHPUnit\Framework\MockObject\MockObject, TestStub_CacheItemInterface_51dba40f returned

/home/dkarlovi/Development/RandD/phpunit-5593/TestCase.php:21

Same issue with PHP 8.3.0 and PHPUnit 10.5.0

@sebastianbergmann sebastianbergmann removed the status/waiting-for-feedback Waiting for feedback from original reporter label Dec 1, 2023
@sebastianbergmann
Copy link
Owner

Your reproducing test case can be simplified to this:

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

interface AnInterface
{
    public function doSomething(): AnotherInterface;
}

interface AnotherInterface
{
    public function doSomethingElse(): static;
}

final class Issue5593Test extends TestCase
{
    public function testOne(): void
    {
        $a = $this->createStub(AnInterface::class);

        $b = $a->doSomething();

        $b->doSomethingElse();
    }
}
  • After $a = $this->createStub(AnInterface::class); you have a test stub of AnInterface in $a
  • $a->doSomething() is called and no return value has been configured for AnInterface::doSomething()
  • The declared return type for AnInterface::doSomething() is AnotherInterface
  • The test double runtime's return value generator generates a test stub for AnotherInterface
  • After $b = $a->doSomething(); you have a test stub of AnotherInterface in $b
  • $b->doSomethingElse(); is called and no return value has been configured for AnotherInterface::doSomethingElse()
  • The declared return type for AnotherInterface::doSomethingElse() is static
  • The test double runtime's return value generator generates a test stub for AnotherInterface to satisfy the static return type declaration
  • However, this test stub for AnotherInterface causes the test double runtime to error

@sebastianbergmann sebastianbergmann self-assigned this Dec 1, 2023
@sebastianbergmann sebastianbergmann changed the title Mocking interfaces which return static impossible Return Value Generator fails to correctly create test stub for method with static return type declaration when used recursively Dec 1, 2023
@dkarlovi
Copy link
Author

dkarlovi commented Dec 1, 2023

Why not add the simplified version as a test case?

@sebastianbergmann
Copy link
Owner

Rest assured, there will be a test case. But that one does not fit and I do not have the time right now to write the test I have in mind.

sebastianbergmann added a commit that referenced this issue Dec 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature/test-doubles Test Stubs and Mock Objects type/bug Something is broken version/10 Something affects PHPUnit 10
Projects
None yet
Development

No branches or pull requests

2 participants