我正在尝试模拟一个php #box
但是因为它被声明为final class
我一直收到这个错误:
final
在没有引入任何新框架的情况下,有没有为我的单元测试解决这个PHPUnit_Framework_Exception: Class "Doctrine\ORM\Query" is declared "final" and cannot be mocked.
行为?
答案 0 :(得分:12)
由于您提到您不想使用任何其他框架,因此您只留下一个选项:uopz
uopz是runkit-and-scary-stuff类型的黑魔术扩展,旨在帮助QA基础设施。
uopz_flags是一个可以修改函数,方法和类的标志的函数。
bool(false)
将产生
;(function($) {
$.fn.fixMe = function() {
return this.each(function() {
var $this = $(this),
$t_fixed;
function init() {
$this.wrap('<div class="container" />');
$t_fixed = $this.clone();
$t_fixed.find("tbody").remove().end().addClass("fixed").insertBefore($this);
resizeFixed();
}
function resizeFixed() {
$t_fixed.find("th").each(function(index) {
$(this).css("width",$this.find("th").eq(index).outerWidth()+"px");
});
}
function scrollFixed() {
var offset = $(this).scrollTop(),
tableOffsetTop = $this.offset().top,
tableOffsetBottom = tableOffsetTop + $this.height() - $this.find("thead").height();
if(offset < tableOffsetTop || offset > tableOffsetBottom)
$t_fixed.hide();
else if(offset >= tableOffsetTop && offset <= tableOffsetBottom && $t_fixed.is(":hidden"))
$t_fixed.show();
}
$(window).resize(resizeFixed);
$(window).scroll(scrollFixed);
init();
});
};
})(jQuery);
$(document).ready(function(){
$("table").fixMe();
$(".up").click(function() {
$('html, body').animate({
scrollTop: 0
}, 2000);
});
});
答案 1 :(得分:11)
正在寻找此特定学说查询模拟答案的人的延迟回复。
你不能模仿Doctrine \ ORM \ Query,因为它的“最终”声明,但如果你查看Query类代码,那么你会看到它扩展的AbstractQuery类,并且不应该有任何问题嘲笑它。
/** @var \PHPUnit_Framework_MockObject_MockObject|AbstractQuery $queryMock */
$queryMock = $this
->getMockBuilder('Doctrine\ORM\AbstractQuery')
->disableOriginalConstructor()
->setMethods(['getResult'])
->getMockForAbstractClass();
答案 2 :(得分:7)
我建议您查看mockery testing framework针对此情况的解决方法:Dealing with Final Classes/Methods:
您可以通过传递实例化对象来创建代理模拟 希望模拟成\ Mockery :: mock(),即Mockery将生成一个 代理到真实对象并有选择地拦截方法调用 设定和满足期望的目的。
例如,这允许做这样的事情:
>>> print "\n".join("%-16s : %s" % (e, getattr(function, e)) for e in dir(function) if not e.startswith("_") and e != "func_globals")
func_closure : (<cell at 0x2b919f6bc050: list object at [...]>,)
func_code : <code object inner at [...], file "<stdin>", line 4>
func_defaults : None
func_dict : {}
func_doc : None
func_name : inner
我不知道你需要做什么,但我希望这有帮助
答案 3 :(得分:2)
有趣的方式:)
PHP7.1,PHPUnit5.7
<?php
use Doctrine\ORM\Query;
//...
$originalQuery = new Query($em);
$allOriginalMethods = get_class_methods($originalQuery);
// some "unmockable" methods will be skipped
$skipMethods = [
'__construct',
'staticProxyConstructor',
'__get',
'__set',
'__isset',
'__unset',
'__clone',
'__sleep',
'__wakeup',
'setProxyInitializer',
'getProxyInitializer',
'initializeProxy',
'isProxyInitialized',
'getWrappedValueHolderValue',
'create',
];
// list of all methods of Query object
$originalMethods = [];
foreach ($allOriginalMethods as $method) {
if (!in_array($method, $skipMethods)) {
$originalMethods[] = $method;
}
}
// Very dummy mock
$queryMock = $this
->getMockBuilder(\stdClass::class)
->setMethods($originalMethods)
->getMock()
;
foreach ($originalMethods as $method) {
// skip "unmockable"
if (in_array($method, $skipMethods)) {
continue;
}
// mock methods you need to be mocked
if ('getResult' == $method) {
$queryMock->expects($this->any())
->method($method)
->will($this->returnCallback(
function (...$args) {
return [];
}
)
);
continue;
}
// make proxy call to rest of the methods
$queryMock->expects($this->any())
->method($method)
->will($this->returnCallback(
function (...$args) use ($originalQuery, $method, $queryMock) {
$ret = call_user_func_array([$originalQuery, $method], $args);
// mocking "return $this;" from inside $originalQuery
if (is_object($ret) && get_class($ret) == get_class($originalQuery)) {
if (spl_object_hash($originalQuery) == spl_object_hash($ret)) {
return $queryMock;
}
throw new \Exception(
sprintf(
'Object [%s] of class [%s] returned clone of itself from method [%s]. Not supported.',
spl_object_hash($originalQuery),
get_class($originalQuery),
$method
)
);
}
return $ret;
}
))
;
}
return $queryMock;
答案 4 :(得分:2)
我实施了@Vadym方法并对其进行了更新。现在我用它来成功测试!
MenuOption
答案 5 :(得分:2)
我偶然发现了Doctrine\ORM\Query
的同样问题。我需要对以下代码进行单元测试:
public function someFunction()
{
// EntityManager was injected in the class
$query = $this->entityManager
->createQuery('SELECT t FROM Test t')
->setMaxResults(1);
$result = $query->getOneOrNullResult();
...
}
createQuery
返回Doctrine\ORM\Query
个对象。我无法使用Doctrine\ORM\AbstractQuery
进行模拟,因为它没有setMaxResults
方法,我也不想引入任何其他框架。
为了克服对类的final
限制,我在PHP 7中使用anonymous classes,这非常容易创建。在我的测试用例类中,我有:
private function getMockDoctrineQuery($result)
{
$query = new class($result) extends AbstractQuery {
private $result;
/**
* Overriding original constructor.
*/
public function __construct($result)
{
$this->result = $result;
}
/**
* Overriding setMaxResults
*/
public function setMaxResults($maxResults)
{
return $this;
}
/**
* Overriding getOneOrNullResult
*/
public function getOneOrNullResult($hydrationMode = null)
{
return $this->result;
}
/**
* Defining blank abstract method to fulfill AbstractQuery
*/
public function getSQL(){}
/**
* Defining blank abstract method to fulfill AbstractQuery
*/
protected function _doExecute(){}
};
return $query;
}
然后在我的测试中:
public function testSomeFunction()
{
// Mocking doctrine Query object
$result = new \stdClass;
$mockQuery = $this->getMockQuery($result);
// Mocking EntityManager
$entityManager = $this->getMockBuilder(EntityManagerInterface::class)->getMock();
$entityManager->method('createQuery')->willReturn($mockQuery);
...
}
答案 6 :(得分:2)
答案 7 :(得分:1)
当您要模拟最终课程时,这是使用Dependency inversion principle的绝佳时机:
应该依靠抽象而不是凝结。
进行模拟意味着:创建一个抽象(接口或抽象类)并将其分配给最终类,然后模拟该抽象。
答案 8 :(得分:0)
我看到您正在使用PHPUnit。您可以使用bypass finals from this answer。
设置仅比bootstrap.php
多一点。阅读How to Mock Final Classes in PHPUnit中的“为什么”。
这是“如何”↓
您需要将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();
}
}
更新phpunit.xml
:
<phpunit bootstrap="vendor/autoload.php">
<extensions>
<extension class="Hook\BypassFinalHook"/>
</extensions>
</phpunit>
然后您可以模拟任何最后的课程: