From faa151591829c954dbcbb9c86f761312ad0d53a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jusi=C4=99ga?= Date: Thu, 8 Dec 2022 11:04:06 +0100 Subject: [PATCH] Implement logic to blocks readonly classes to be doubled. --- .../Exception/ClassIsReadonlyException.php | 28 +++++++++++++++++++ src/Framework/MockObject/Generator.php | 10 +++++++ src/Framework/MockObject/MockBuilder.php | 1 + tests/_files/mock-object/ReadonlyClass.php | 22 +++++++++++++++ .../Framework/MockObject/GeneratorTest.php | 9 ++++++ 5 files changed, 70 insertions(+) create mode 100644 src/Framework/MockObject/Exception/ClassIsReadonlyException.php create mode 100644 tests/_files/mock-object/ReadonlyClass.php diff --git a/src/Framework/MockObject/Exception/ClassIsReadonlyException.php b/src/Framework/MockObject/Exception/ClassIsReadonlyException.php new file mode 100644 index 00000000000..aa1a74510de --- /dev/null +++ b/src/Framework/MockObject/Exception/ClassIsReadonlyException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsReadonlyException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" is declared "readonly" and cannot be doubled', + $className + ) + ); + } +} diff --git a/src/Framework/MockObject/Generator.php b/src/Framework/MockObject/Generator.php index 0674068b502..17e3312c573 100644 --- a/src/Framework/MockObject/Generator.php +++ b/src/Framework/MockObject/Generator.php @@ -27,6 +27,7 @@ use function is_array; use function is_object; use function md5; +use function method_exists; use function mt_rand; use function preg_match; use function preg_match_all; @@ -146,6 +147,7 @@ public function __clone() * @throws \PHPUnit\Framework\InvalidArgumentException * @throws ClassAlreadyExistsException * @throws ClassIsFinalException + * @throws ClassIsReadonlyException * @throws DuplicateMethodException * @throws InvalidMethodNameException * @throws OriginalConstructorInvocationRequiredException @@ -299,6 +301,7 @@ public function getMockForInterfaces(array $interfaces, bool $callAutoload = tru * @throws \PHPUnit\Framework\InvalidArgumentException * @throws ClassAlreadyExistsException * @throws ClassIsFinalException + * @throws ClassIsReadonlyException * @throws DuplicateMethodException * @throws InvalidMethodNameException * @throws OriginalConstructorInvocationRequiredException @@ -360,6 +363,7 @@ interface_exists($originalClassName, $callAutoload)) { * @throws \PHPUnit\Framework\InvalidArgumentException * @throws ClassAlreadyExistsException * @throws ClassIsFinalException + * @throws ClassIsReadonlyException * @throws DuplicateMethodException * @throws InvalidMethodNameException * @throws OriginalConstructorInvocationRequiredException @@ -442,6 +446,7 @@ public function getObjectForTrait(string $traitName, string $traitClassName = '' /** * @throws ClassIsFinalException + * @throws ClassIsReadonlyException * @throws ReflectionException * @throws RuntimeException */ @@ -764,6 +769,7 @@ private function getObject(MockType $mockClass, $type = '', bool $callOriginalCo /** * @throws ClassIsFinalException + * @throws ClassIsReadonlyException * @throws ReflectionException * @throws RuntimeException */ @@ -819,6 +825,10 @@ private function generateMock(string $type, ?array $explicitMethods, string $moc throw new ClassIsFinalException($_mockClassName['fullClassName']); } + if (method_exists($class, 'isReadOnly') && $class->isReadOnly()) { + throw new ClassIsReadonlyException($_mockClassName['fullClassName']); + } + // @see https://github.com/sebastianbergmann/phpunit/issues/2995 if ($isInterface && $class->implementsInterface(Throwable::class)) { $actualClassName = Exception::class; diff --git a/src/Framework/MockObject/MockBuilder.php b/src/Framework/MockObject/MockBuilder.php index 54aa3b9488d..4007a9f13d7 100644 --- a/src/Framework/MockObject/MockBuilder.php +++ b/src/Framework/MockObject/MockBuilder.php @@ -114,6 +114,7 @@ public function __construct(TestCase $testCase, $type) * @throws \PHPUnit\Framework\InvalidArgumentException * @throws ClassAlreadyExistsException * @throws ClassIsFinalException + * @throws ClassIsReadonlyException * @throws DuplicateMethodException * @throws InvalidMethodNameException * @throws OriginalConstructorInvocationRequiredException diff --git a/tests/_files/mock-object/ReadonlyClass.php b/tests/_files/mock-object/ReadonlyClass.php new file mode 100644 index 00000000000..e968645c547 --- /dev/null +++ b/tests/_files/mock-object/ReadonlyClass.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +readonly class ReadonlyClass +{ + public function __construct(private mixed $value) + { + } + + public function value(): mixed + { + return $this->value; + } +} diff --git a/tests/unit/Framework/MockObject/GeneratorTest.php b/tests/unit/Framework/MockObject/GeneratorTest.php index b93a4e72f54..a46ac411036 100644 --- a/tests/unit/Framework/MockObject/GeneratorTest.php +++ b/tests/unit/Framework/MockObject/GeneratorTest.php @@ -25,6 +25,7 @@ use PHPUnit\TestFixture\FinalClass; use PHPUnit\TestFixture\InterfaceWithSemiReservedMethodName; use PHPUnit\TestFixture\MockObject\AbstractMockTestClass; +use PHPUnit\TestFixture\MockObject\ReadonlyClass; use PHPUnit\TestFixture\SingletonClass; use RuntimeException; use stdClass; @@ -325,6 +326,14 @@ public function testCannotMockFinalClass(): void $this->createMock(FinalClass::class); } + public function testCannotMockReadonlyClass(): void + { + $this->expectException(ClassIsReadonlyException::class); + + /* @noinspection ClassMockingCorrectnessInspection */ + $this->createMock(ReadonlyClass::class); + } + public function testCanDoubleIntersectionOfMultipleInterfaces(): void { $stub = $this->generator->getMockForInterfaces(