From 97b3278e65863e53066f31adb85d92334c0ea07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bundyra?= Date: Mon, 6 Mar 2023 17:25:07 +0000 Subject: [PATCH] feat: Support for PHPUnit 10 (#56) --- .github/workflows/tests.yml | 71 +++++++++++++- autoload.php | 20 +++- .../DefaultArgumentRemoverReturnTypes100.php | 81 ++++++++++++++++ classes/MockDisablerPHPUnit10.php | 48 ++++++++++ classes/MockObjectProxyReturnTypes100.php | 92 +++++++++++++++++++ classes/PHPMock.php | 18 ++++ composer.json | 6 +- tests/MockObjectProxyTest.php | 42 +++++---- tests/PHPMockTest.php | 4 +- 9 files changed, 355 insertions(+), 27 deletions(-) create mode 100644 classes/DefaultArgumentRemoverReturnTypes100.php create mode 100644 classes/MockDisablerPHPUnit10.php create mode 100644 classes/MockObjectProxyReturnTypes100.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 83dbe1e..0fed7f4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,7 @@ jobs: strategy: matrix: php-version: + - '8.2' - '8.1' - '8.0' - '7.4' @@ -21,6 +22,8 @@ jobs: - '7.1' - '7.0' phpunit-version: + - '10.0.0' + - '9.6.0' - '9.5.0' - '9.4.0' - '9.3.0' @@ -46,6 +49,52 @@ jobs: - '6.0.0' exclude: + # PHP 8.2 Exclusions + - php-version: '8.2' + phpunit-version: '9.4.0' + - php-version: '8.2' + phpunit-version: '9.3.0' + - php-version: '8.2' + phpunit-version: '9.2.0' + - php-version: '8.2' + phpunit-version: '9.1.0' + - php-version: '8.2' + phpunit-version: '9.0.0' + - php-version: '8.2' + phpunit-version: '8.4.0' + - php-version: '8.2' + phpunit-version: '8.3.0' + - php-version: '8.2' + phpunit-version: '8.2.0' + - php-version: '8.2' + phpunit-version: '8.1.0' + - php-version: '8.2' + phpunit-version: '8.0.0' + - php-version: '8.2' + phpunit-version: '7.5.0' + - php-version: '8.2' + phpunit-version: '7.4.0' + - php-version: '8.2' + phpunit-version: '7.3.0' + - php-version: '8.2' + phpunit-version: '7.2.0' + - php-version: '8.2' + phpunit-version: '7.1.0' + - php-version: '8.2' + phpunit-version: '7.0.0' + - php-version: '8.2' + phpunit-version: '6.5.0' + - php-version: '8.2' + phpunit-version: '6.4.0' + - php-version: '8.2' + phpunit-version: '6.3.0' + - php-version: '8.2' + phpunit-version: '6.2.0' + - php-version: '8.2' + phpunit-version: '6.1.0' + - php-version: '8.2' + phpunit-version: '6.0.0' + # PHP 8.1 Exclusions - php-version: '8.1' phpunit-version: '9.4.0' @@ -93,6 +142,8 @@ jobs: phpunit-version: '6.0.0' # PHP 8.0 Exclusions + - php-version: '8.0' + phpunit-version: '10.0.0' - php-version: '8.0' phpunit-version: '9.2.0' - php-version: '8.0' @@ -135,6 +186,8 @@ jobs: phpunit-version: '6.0.0' # PHP 7.4 Exclusions + - php-version: '7.4' + phpunit-version: '10.0.0' - php-version: '7.4' phpunit-version: '8.1.0' - php-version: '7.4' @@ -161,8 +214,16 @@ jobs: phpunit-version: '6.1.0' - php-version: '7.4' phpunit-version: '6.0.0' + + # PHP 7.3 Exclusions + - php-version: '7.3' + phpunit-version: '10.0.0' # PHP 7.2 Exclusions + - php-version: '7.2' + phpunit-version: '10.0.0' + - php-version: '7.2' + phpunit-version: '9.6.0' - php-version: '7.2' phpunit-version: '9.5.0' - php-version: '7.2' @@ -177,6 +238,10 @@ jobs: phpunit-version: '9.0.0' # PHP 7.1 Exclusions + - php-version: '7.1' + phpunit-version: '10.0.0' + - php-version: '7.1' + phpunit-version: '9.6.0' - php-version: '7.1' phpunit-version: '9.5.0' - php-version: '7.1' @@ -203,6 +268,10 @@ jobs: phpunit-version: '8.0.0' # PHP 7.0 Exclusions + - php-version: '7.0' + phpunit-version: '10.0.0' + - php-version: '7.0' + phpunit-version: '9.6.0' - php-version: '7.0' phpunit-version: '9.5.0' - php-version: '7.0' @@ -244,8 +313,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - with: - ref: ${{ github.head_ref }} - name: Install PHP uses: shivammathur/setup-php@v2 diff --git a/autoload.php b/autoload.php index b98a1fb..e59f8ca 100644 --- a/autoload.php +++ b/autoload.php @@ -67,7 +67,16 @@ class_alias( ); } -if (! class_exists(\PHPUnit\Framework\BaseTestListener::class)) { +$hasVersion = class_exists(\PHPUnit\Runner\Version::class); + +if ($hasVersion + && version_compare(\PHPUnit\Runner\Version::id(), '10.0.0') >= 0 +) { + class_alias( + phpmock\phpunit\MockDisablerPHPUnit10::class, + phpmock\phpunit\MockDisabler::class + ); +} elseif (! class_exists(\PHPUnit\Framework\BaseTestListener::class)) { include __DIR__ . '/compatibility/BaseTestListener.php'; class_alias( phpmock\phpunit\MockDisablerPHPUnit7::class, @@ -80,12 +89,17 @@ class_alias( ); } -if (class_exists(\PHPUnit\Runner\Version::class) +if ($hasVersion + && version_compare(\PHPUnit\Runner\Version::id(), '10.0.0') >= 0 +) { + class_alias(\phpmock\phpunit\DefaultArgumentRemoverReturnTypes100::class, \phpmock\phpunit\DefaultArgumentRemover::class); + class_alias(\phpmock\phpunit\MockObjectProxyReturnTypes100::class, \phpmock\phpunit\MockObjectProxy::class); +} elseif ($hasVersion && version_compare(\PHPUnit\Runner\Version::id(), '8.4.0') >= 0 ) { class_alias(\phpmock\phpunit\DefaultArgumentRemoverReturnTypes84::class, \phpmock\phpunit\DefaultArgumentRemover::class); class_alias(\phpmock\phpunit\MockObjectProxyReturnTypes84::class, \phpmock\phpunit\MockObjectProxy::class); -} elseif (class_exists(\PHPUnit\Runner\Version::class) +} elseif ($hasVersion && version_compare(\PHPUnit\Runner\Version::id(), '8.1.0') >= 0 ) { class_alias(\phpmock\phpunit\DefaultArgumentRemoverReturnTypes::class, \phpmock\phpunit\DefaultArgumentRemover::class); diff --git a/classes/DefaultArgumentRemoverReturnTypes100.php b/classes/DefaultArgumentRemoverReturnTypes100.php new file mode 100644 index 0000000..41fdcba --- /dev/null +++ b/classes/DefaultArgumentRemoverReturnTypes100.php @@ -0,0 +1,81 @@ + + * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations + * @license http://www.wtfpl.net/txt/copying/ WTFPL + * @internal + */ +class DefaultArgumentRemoverReturnTypes100 extends InvocationOrder +{ + /** + * @SuppressWarnings(PHPMD) + */ + public function invokedDo(Invocation $invocation): void + { + } + + /** + * @SuppressWarnings(PHPMD) + */ + public function matches(Invocation $invocation) : bool + { + $iClass = class_exists(Invocation::class); + + if ($iClass + || $invocation instanceof Invocation\StaticInvocation + ) { + $this->removeDefaultArguments( + $invocation, + $iClass ? Invocation::class : Invocation\StaticInvocation::class + ); + } else { + MockFunctionGenerator::removeDefaultArguments($invocation->parameters); + } + + return false; + } + + public function verify() : void + { + } + + /** + * This method is not defined in the interface, but used in + * PHPUnit_Framework_MockObject_InvocationMocker::hasMatchers(). + * + * @return boolean + * @see \PHPUnit_Framework_MockObject_InvocationMocker::hasMatchers() + */ + public function hasMatchers() + { + return false; + } + + public function toString() : string + { + return __CLASS__; + } + + /** + * Remove default arguments from StaticInvocation or its children (hack) + * + * @SuppressWarnings(PHPMD) + */ + private function removeDefaultArguments(Invocation $invocation, string $class) + { + $remover = function () { + MockFunctionGenerator::removeDefaultArguments($this->parameters); + }; + + $remover->bindTo($invocation, $class)(); + } +} diff --git a/classes/MockDisablerPHPUnit10.php b/classes/MockDisablerPHPUnit10.php new file mode 100644 index 0000000..be536b0 --- /dev/null +++ b/classes/MockDisablerPHPUnit10.php @@ -0,0 +1,48 @@ + + * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations + * @license http://www.wtfpl.net/txt/copying/ WTFPL + * @internal + */ +class MockDisablerPHPUnit10 implements FinishedSubscriber +{ + /** + * @var Deactivatable The function mocks. + */ + private $deactivatable; + + /** + * Sets the function mocks. + * + * @param Deactivatable $deactivatable The function mocks. + */ + public function __construct(Deactivatable $deactivatable) + { + $this->deactivatable = $deactivatable; + } + + /** + * @SuppressWarnings(PHPMD) + */ + public function notify(Finished $event) : void + { + $this->deactivatable->disable(); + } + + public function endTest(): void + { + $this->deactivatable->disable(); + } +} diff --git a/classes/MockObjectProxyReturnTypes100.php b/classes/MockObjectProxyReturnTypes100.php new file mode 100644 index 0000000..d6e8977 --- /dev/null +++ b/classes/MockObjectProxyReturnTypes100.php @@ -0,0 +1,92 @@ + + * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations + * @license http://www.wtfpl.net/txt/copying/ WTFPL + * @internal + */ +class MockObjectProxyReturnTypes100 implements MockObject +{ + /** + * @var MockObject $mockObject The mock object. + */ + private $mockObject; + + /** + * Inject the subject. + * + * @param MockObject $mockObject The subject. + */ + public function __construct(MockObject $mockObject) + { + $this->mockObject = $mockObject; + } + + /** + * @SuppressWarnings(PHPMD) + */ + // @codingStandardsIgnoreStart + public function __phpunit_getInvocationHandler(): InvocationHandler + { + return $this->mockObject->__phpunit_getInvocationHandler(); + } + + /** + * @SuppressWarnings(PHPMD) + */ + // @codingStandardsIgnoreStart + public function __phpunit_setOriginalObject(object $originalObject) : void + { + // @codingStandardsIgnoreEnd + $this->mockObject->__phpunit_setOriginalObject($originalObject); + } + + /** + * @SuppressWarnings(PHPMD) + */ + // @codingStandardsIgnoreStart + public function __phpunit_verify(bool $unsetInvocationMocker = true) : void + { + // @codingStandardsIgnoreEnd + $this->mockObject->__phpunit_verify($unsetInvocationMocker); + } + + public function expects(InvocationOrder $matcher) : BuilderInvocationMocker + { + return $this->mockObject->expects($matcher)->method(MockDelegateFunctionBuilder::METHOD); + } + + /** + * This method is not part of the contract but was found in + * PHPUnit's mocked_class.tpl.dist. + * + * @SuppressWarnings(PHPMD) + */ + // @codingStandardsIgnoreStart + public function __phpunit_hasMatchers() : bool + { + // @codingStandardsIgnoreEnd + return $this->mockObject->__phpunit_hasMatchers(); + } + + /** + * @SuppressWarnings(PHPMD) + */ + // @codingStandardsIgnoreStart + public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration) : void + { + // @codingStandardsIgnoreEnd + $this->mockObject->__phpunit_setReturnValueGeneration($returnValueGeneration); + } +} diff --git a/classes/PHPMock.php b/classes/PHPMock.php index 9adbfd1..40f08b8 100644 --- a/classes/PHPMock.php +++ b/classes/PHPMock.php @@ -5,7 +5,9 @@ use phpmock\integration\MockDelegateFunctionBuilder; use phpmock\MockBuilder; use phpmock\Deactivatable; +use PHPUnit\Event\Facade; use PHPUnit\Framework\MockObject\MockObject; +use ReflectionProperty; /** * Adds building a function mock functionality into \PHPUnit\Framework\TestCase. @@ -90,9 +92,25 @@ private function addMatcher($mock, $name) * directly created with PHPMock's API. * * @param Deactivatable $deactivatable The function mocks. + * + * @SuppressWarnings(PHPMD.StaticAccess) */ public function registerForTearDown(Deactivatable $deactivatable) { + if (class_exists(Facade::class)) { + $property = new ReflectionProperty(Facade::class, 'sealed'); + $property->setAccessible(true); + $property->setValue(false); + + Facade::registerSubscriber( + new MockDisabler($deactivatable) + ); + + $property->setValue(true); + + return; + } + $result = $this->getTestResultObject(); $result->addListener(new MockDisabler($deactivatable)); } diff --git a/composer.json b/composer.json index ef911d4..3683e25 100644 --- a/composer.json +++ b/composer.json @@ -19,11 +19,11 @@ }, "require": { "php": ">=7", - "phpunit/phpunit": "^6 || ^7 || ^8 || ^9", - "php-mock/php-mock-integration": "^2.1" + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10", + "php-mock/php-mock-integration": "^2.2.1" }, "require-dev": { - "phpspec/prophecy": "^1.10.3" + "mockery/mockery": "^1.3.6" }, "archive": { "exclude": ["/tests"] diff --git a/tests/MockObjectProxyTest.php b/tests/MockObjectProxyTest.php index 41ace7e..e9020d4 100644 --- a/tests/MockObjectProxyTest.php +++ b/tests/MockObjectProxyTest.php @@ -2,6 +2,7 @@ namespace phpmock\phpunit; +use Mockery; use phpmock\integration\MockDelegateFunctionBuilder; use PHPUnit\Framework\MockObject\Builder\InvocationMocker; use PHPUnit\Framework\MockObject\ConfigurableMethod; @@ -24,7 +25,6 @@ */ class MockObjectProxyTest extends TestCase { - /** * Tests expects() * @@ -37,7 +37,7 @@ public function testExpects() $methods = class_exists(ConfigurableMethod::class) ? new ConfigurableMethod( MockDelegateFunctionBuilder::METHOD, - $this->prophesize(Type::class)->reveal() + Mockery::mock(Type::class) ) : [MockDelegateFunctionBuilder::METHOD]; @@ -48,15 +48,14 @@ public function testExpects() $invocationMocker = $invocationHandler->expects($matcher); } else { $invocationMocker = new InvocationMocker( - $this->prophesize(MatcherCollection::class)->reveal(), - $this->prophesize(Invocation::class)->reveal(), + $this->getMockBuilder(MatcherCollection::class)->getMock(), + $this->getMockBuilder(Invocation::class)->getMock(), $methods ); } - $prophecy = $this->prophesize(MockObject::class); - $prophecy->expects($matcher)->willReturn($invocationMocker); - $mock = $prophecy->reveal(); + $mock = Mockery::mock(MockObject::class); + $mock->shouldReceive('expects')->with($matcher)->andReturn($invocationMocker); $proxy = new MockObjectProxy($mock); @@ -71,7 +70,18 @@ public function testExpects() private function getMethodMatcher($invocationMocker) { - if (class_exists(\PHPUnit\Runner\Version::class) + $hasVersion = class_exists(\PHPUnit\Runner\Version::class); + + if ($hasVersion + && version_compare(\PHPUnit\Runner\Version::id(), '10.0.0') >= 0 + ) { + $reflection = new \ReflectionClass(InvocationMocker::class); + $property = $reflection->getProperty('matcher'); + $property->setAccessible(true); + return $property->getValue($invocationMocker)->methodNameRule(); + } + + if ($hasVersion && version_compare(\PHPUnit\Runner\Version::id(), '8.4.0') >= 0 ) { $reflection = new \ReflectionClass(InvocationMocker::class); @@ -96,9 +106,8 @@ private function getMethodMatcher($invocationMocker) */ public function testHasMatcher() { - $prophecy = $this->prophesize(MockObject::class); - $prophecy->__phpunit_hasMatchers()->willReturn(true); - $mock = $prophecy->reveal(); + $mock = Mockery::mock(MockObject::class); + $mock->shouldReceive('__phpunit_hasMatchers')->andReturn(true); $proxy = new MockObjectProxy($mock); @@ -117,13 +126,12 @@ public function testHasMatcher() */ public function testProxiedMethods($method, array $arguments = [], $expected = null) { - $prophecy = $this->prophesize(MockObject::class); + $mock = Mockery::mock(MockObject::class); if ($expected) { - call_user_func_array([$prophecy, $method], $arguments)->willReturn($expected)->shouldBeCalledTimes(1); + $mock->shouldReceive($method)->withArgs($arguments)->andReturn($expected)->times(1); } else { - call_user_func_array([$prophecy, $method], $arguments)->shouldBeCalledTimes(1); + $mock->shouldReceive($method)->withArgs($arguments)->times(1); } - $mock = $prophecy->reveal(); $proxy = new MockObjectProxy($mock); @@ -141,7 +149,7 @@ public function testProxiedMethods($method, array $arguments = [], $expected = n * * @SuppressWarnings(PHPMD) */ - public function provideTestProxiedMethods() + public static function provideTestProxiedMethods() { $return = []; if (class_exists(\PHPUnit\Runner\Version::class) @@ -156,7 +164,7 @@ public function provideTestProxiedMethods() ]; } - $return[] = ['__phpunit_setOriginalObject', ['bar']]; + $return[] = ['__phpunit_setOriginalObject', [new \stdClass()]]; $return[] = ['__phpunit_verify', [true]]; return $return; } diff --git a/tests/PHPMockTest.php b/tests/PHPMockTest.php index 0cfef49..da26159 100644 --- a/tests/PHPMockTest.php +++ b/tests/PHPMockTest.php @@ -2,7 +2,7 @@ namespace phpmock\phpunit; -use phpmock\AbstractMockTest; +use phpmock\AbstractMockTestCase; use PHPUnit\Framework\ExpectationFailedException; /** @@ -13,7 +13,7 @@ * @license http://www.wtfpl.net/txt/copying/ WTFPL * @see PHPMock */ -class PHPMockTest extends AbstractMockTest +class PHPMockTest extends AbstractMockTestCase { use PHPMock;