PHP模拟最终类

Dan*_*bib 28 php phpunit unit-testing mocking doctrine-orm

我试图模拟一个PHP final class但是因为它被声明final我一直收到这个错误:

PHPUnit_Framework_Exception: Class "Doctrine\ORM\Query" is declared "final" and cannot be mocked.

反正final是为了我的单元测试而不引入任何新框架来解决这种行为?

Joe*_*ins 14

既然你提到你不想使用任何其他框架,你只留下一个选择:uopz

uopz是runkit-and-scary-stuff类型的黑魔术扩展,旨在帮助QA基础设施.

uopz_flags是一个可以修改函数,方法和类的标志的函数.

<?php
final class Test {}

/** ZEND_ACC_CLASS is defined as 0, just looks nicer ... **/

uopz_flags(Test::class, null, ZEND_ACC_CLASS);

$reflector = new ReflectionClass(Test::class);

var_dump($reflector->isFinal());
?>
Run Code Online (Sandbox Code Playgroud)

会屈服

bool(false)
Run Code Online (Sandbox Code Playgroud)


wor*_*hit 12

对于正在寻找此特定学说查询模拟答案的人的延迟回复.

你不能模仿Doctrine\ORM\Query,因为它的"最终"声明,但如果你查看Query类代码,那么你会看到它扩展的AbstractQuery类,并且不应该有任何问题嘲笑它.

/** @var \PHPUnit_Framework_MockObject_MockObject|AbstractQuery $queryMock */
$queryMock = $this
    ->getMockBuilder('Doctrine\ORM\AbstractQuery')
    ->disableOriginalConstructor()
    ->setMethods(['getResult'])
    ->getMockForAbstractClass();
Run Code Online (Sandbox Code Playgroud)


Mat*_*teo 7

我建议你看看嘲讽测试框架,它有一个解决方法,可以解决页面中描述的这种情况:处理最终的类/方法:

您可以通过将您希望模拟的实例化对象传递到\ Mockery :: mock()来创建代理模拟,即Mockery将为真实对象生成代理并有选择地拦截方法调用以设置和满足期望.

例如,这允许做这样的事情:

class MockFinalClassTest extends \PHPUnit_Framework_TestCase {

    public function testMock()
    {
        $em = \Mockery::mock("Doctrine\ORM\EntityManager");

        $query = new Doctrine\ORM\Query($em);
        $proxy = \Mockery::mock($query);
        $this->assertNotNull($proxy);

        $proxy->setMaxResults(4);
        $this->assertEquals(4, $query->getMaxResults());
    }
Run Code Online (Sandbox Code Playgroud)

我不知道你需要做什么,但我希望这有帮助

  • 当模拟类具有最终依赖项的最终依赖项时,它会变得一团糟。它似乎只对没有依赖关系的最终类有用。 (2认同)

Fab*_*one 7

当你想模拟一个 final 类时,这是利用依赖倒置原则的最佳时机:

人们应该依赖抽象,而不是具体。

对于模拟,它意味着:创建一个抽象(接口或抽象类)并将其分配给最终类,并模拟抽象。

  • 在这种情况下,您应该适应。在您的项目中创建一个新接口,它将成为您的可模拟依赖项,而不是第 3 方依赖项。新接口的实现将注入不可模拟的第 3 方依赖项。实现不应该有任何逻辑,只作为第三方类方法的网关,不值得单元测试。 (3认同)
  • 好的,但是如果最终类属于第三方库,则不能简单地对其进行编辑。然而我发现:如果你在你的代码中创建了一个接口,而最终的类只是_发生来实现它_ - 即它具有相同的方法签名但没有`implements`关键字,它仍然可以工作:) (2认同)

Mil*_*ilo 5

为此目的,有一个小型图书馆绕过决赛。由博客文章详细描述。

只需要做的就是在加载类之前启用此实用程序:

DG\BypassFinals::enable();
Run Code Online (Sandbox Code Playgroud)

  • 该工具本身现在可与PHP 5.4一起使用。您可以下载唯一需要的文件“ BypassFinals.php”并将其包含在内。 (2认同)

Tom*_*uba 5

2019 年 PHPUnit 答案

我看到您正在使用 PHPUnit。您可以使用此答案中的绕过决赛

设置只是多一点bootstrap.php。阅读如何在 PHPUnit 中模拟最终类中的“为什么” 。


这里是“如何”?

2 步

您需要将 Hook 与旁路呼叫一起使用:

<?php declare(strict_types=1);

use DG\BypassFinals;
use PHPUnit\Runner\BeforeTestHook;

final class BypassFinalHook implements BeforeTestHook
{
    public function executeBeforeTest(string $test): void
    {
        BypassFinals::enable();
    }
}
Run Code Online (Sandbox Code Playgroud)

更新phpunit.xml

<phpunit bootstrap="vendor/autoload.php">
    <extensions>
        <extension class="Hook\BypassFinalHook"/>
    </extensions>
</phpunit>
Run Code Online (Sandbox Code Playgroud)

然后你可以模拟任何最终课程

在此处输入图片说明