Skip to content

Commit

Permalink
Implement logic to blocks readonly classes to be doubled.
Browse files Browse the repository at this point in the history
  • Loading branch information
michaljusiega authored and sebastianbergmann committed Dec 9, 2022
1 parent 5c6e811 commit faa1515
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 0 deletions.
28 changes: 28 additions & 0 deletions src/Framework/MockObject/Exception/ClassIsReadonlyException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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
)
);
}
}
10 changes: 10 additions & 0 deletions src/Framework/MockObject/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -146,6 +147,7 @@ public function __clone()
* @throws \PHPUnit\Framework\InvalidArgumentException
* @throws ClassAlreadyExistsException
* @throws ClassIsFinalException
* @throws ClassIsReadonlyException
* @throws DuplicateMethodException
* @throws InvalidMethodNameException
* @throws OriginalConstructorInvocationRequiredException
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -360,6 +363,7 @@ interface_exists($originalClassName, $callAutoload)) {
* @throws \PHPUnit\Framework\InvalidArgumentException
* @throws ClassAlreadyExistsException
* @throws ClassIsFinalException
* @throws ClassIsReadonlyException
* @throws DuplicateMethodException
* @throws InvalidMethodNameException
* @throws OriginalConstructorInvocationRequiredException
Expand Down Expand Up @@ -442,6 +446,7 @@ public function getObjectForTrait(string $traitName, string $traitClassName = ''

/**
* @throws ClassIsFinalException
* @throws ClassIsReadonlyException
* @throws ReflectionException
* @throws RuntimeException
*/
Expand Down Expand Up @@ -764,6 +769,7 @@ private function getObject(MockType $mockClass, $type = '', bool $callOriginalCo

/**
* @throws ClassIsFinalException
* @throws ClassIsReadonlyException
* @throws ReflectionException
* @throws RuntimeException
*/
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/Framework/MockObject/MockBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions tests/_files/mock-object/ReadonlyClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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;
}
}
9 changes: 9 additions & 0 deletions tests/unit/Framework/MockObject/GeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit faa1515

Please sign in to comment.