PHP Mocking Final Class
Solution 1
Since you mentioned you don't want to use any other framework, you are only leaving yourself one option: uopz
uopz is a black magic extension of the runkit-and-scary-stuff genre, intended to help with QA infrastructure.
uopz_flags is a function that can modify the flags of functions, methods and classes.
<?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());
?>
Will yield
bool(false)
Solution 2
Late response for someone who is looking for this specific doctrine query mock answer.
You can not mock Doctrine\ORM\Query because its "final" declaration, but if you look into Query class code then you will see that its extending AbstractQuery class and there should not be any problems mocking it.
/** @var \PHPUnit_Framework_MockObject_MockObject|AbstractQuery $queryMock */
$queryMock = $this
->getMockBuilder('Doctrine\ORM\AbstractQuery')
->disableOriginalConstructor()
->setMethods(['getResult'])
->getMockForAbstractClass();
Solution 3
I suggest you to take a look at the mockery testing framework that have a workaround for this situation described in the page: Dealing with Final Classes/Methods:
You can create a proxy mock by passing the instantiated object you wish to mock into \Mockery::mock(), i.e. Mockery will then generate a Proxy to the real object and selectively intercept method calls for the purposes of setting and meeting expectations.
As example this permit to do something like this:
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());
}
I don't know what you need to do but, i hope this help
Solution 4
There is a small library Bypass Finals exactly for such purpose. Described in detail by blog post.
Only you have to do is enable this utility before classes are loaded:
DG\BypassFinals::enable();
Solution 5
2019 answer for PHPUnit
I see you're using PHPUnit. You can use bypass finals from this answer.
The setup is just a bit more than bootstrap.php
. Read "why" in How to Mock Final Classes in PHPUnit.
Here is "how" ↓
2 Steps
You need to use Hook with bypass call:
<?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();
}
}
Update phpunit.xml
:
<phpunit bootstrap="vendor/autoload.php">
<extensions>
<extension class="Hook\BypassFinalHook"/>
</extensions>
</phpunit>
Then you can mock any final class:
DanHabib
React/iOS in the front, PHP/Python/Aerospike/Mysql in the back Enjoy Traveling, Reading, and Running
Updated on March 30, 2020Comments
-
DanHabib about 4 years
I am attempting to mock a php
final class
but since it is declaredfinal
I keep receiving this error:PHPUnit_Framework_Exception: Class "Doctrine\ORM\Query" is declared "final" and cannot be mocked.
Is there anyway to get around this
final
behavior just for my unit tests without introducing any new frameworks? -
b01 almost 7 yearsThis works for a class marked final that extend an abstract or implement an interface. If the class itself is being defined final then you'll have to use one of the other work-a-rounds.
-
BentCoder about 6 yearsMuch cleaner compared to all the others. +1
-
Geoff Maddock over 5 yearsThis solution looks pretty good, but I'm trying to get it to work with codeception, and finding this error when I call 'getResult': Trying to configure method "getResult" which cannot be configured because it does not exist, has not been specified, is final, or is static
-
Geoff Maddock over 5 yearsAfter a little more digging - using 'execute' rather than 'getResult' allowed this to work!
-
afilina over 5 yearsIs there any alternative for php 5.5?
-
Milo over 5 yearsThe tool itself works with PHP 5.4 today. You can download the
BypassFinals.php
, the only required file, and include it. -
fabpico about 5 yearsWhen the mocking class has final dependencies that has final dependencies its getting a mess. It seems only useful for final classes that has no dependencies.
-
pstryk almost 5 yearsOK, but if the final class belongs to third party library, you cannot just simply edit it. What I found out however: if you create an interface in your code, and the final class just happens to implement it - i.e. it has the same method signatures but no
implements
keyword, it will still work :) -
fabpico almost 5 yearsIn this case you should adapt. Create an new interface in your project that will be your mockable dependency instead of the 3rd party dependency. The implementation of the new interface will then inject the unmockable 3rd party dependency. The implementation should not have any logic, only act as gateway to the 3rd party class methods, that is not worth to unit test.
-
pstryk almost 5 yearsWell, I must retract what I just said. The tests are passing, yes, but just because I injected what I wanted and made assertions that rely on it. However, application is not working anymore, because of error: method should return instance of my interface, but it just got instance of said final class instead. So it looks the
implements
keyword is necessary. In this situation your proposed solution seems to be the only sensible way. Thank you :) -
Alexander almost 5 yearsDG\BypassFinals utilizes
stream_wrapper_register
. This could prevent tools (f.e. Infection) relying on the same mechanism from working correctly. -
k0pernikus over 3 yearsThere's a more indepth article available here: tomasvotruba.com/blog/2019/03/28/…