diff --git a/ChangeLog-11.2.md b/ChangeLog-11.2.md index a97c02ea31e..91517699755 100644 --- a/ChangeLog-11.2.md +++ b/ChangeLog-11.2.md @@ -2,6 +2,13 @@ All notable changes of the PHPUnit 11.2 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. +## [11.2.1] - 2024-06-11 + +### Fixed + +* [#5857](https://github.com/sebastianbergmann/phpunit/issues/5857): Mocked methods cannot be called from the original constructor of a partially mocked class +* [#5859](https://github.com/sebastianbergmann/phpunit/issues/5859): XML Configuration File Migrator does not remove `cacheDirectory` attribute from `` element when migrating from PHPUnit 11.1 to PHPUnit 11.2 + ## [11.2.0] - 2024-06-07 ### Added @@ -14,4 +21,5 @@ All notable changes of the PHPUnit 11.2 release series are documented in this fi * [#5800](https://github.com/sebastianbergmann/phpunit/issues/5800): Support for targeting traits with `#[CoversClass]` and `#[UsesClass]` attributes +[11.2.1]: https://github.com/sebastianbergmann/phpunit/compare/11.2.0...11.2.1 [11.2.0]: https://github.com/sebastianbergmann/phpunit/compare/11.1.3...11.2.0 diff --git a/src/Framework/MockObject/Generator/Generator.php b/src/Framework/MockObject/Generator/Generator.php index cceedb418ec..5ca0c790dfc 100644 --- a/src/Framework/MockObject/Generator/Generator.php +++ b/src/Framework/MockObject/Generator/Generator.php @@ -578,7 +578,20 @@ private function userDefinedInterfaceMethods(string $interfaceName): array private function getObject(MockType $mockClass, string $type = '', bool $callOriginalConstructor = false, array $arguments = [], bool $callOriginalMethods = false, ?object $proxyTarget = null, bool $returnValueGeneration = true): object { $className = $mockClass->generate(); - $object = $this->instantiate($className, $callOriginalConstructor, $arguments); + + try { + $object = (new ReflectionClass($className))->newInstanceWithoutConstructor(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + + $reflector = new ReflectionObject($object); if ($object instanceof StubInternal && $mockClass instanceof MockClass) { /** @@ -586,7 +599,7 @@ private function getObject(MockType $mockClass, string $type = '', bool $callOri * * @noinspection PhpUnhandledExceptionInspection */ - (new ReflectionObject($object))->getProperty('__phpunit_state')->setValue( + $reflector->getProperty('__phpunit_state')->setValue( $object, new TestDoubleState($mockClass->configurableMethods(), $returnValueGeneration), ); @@ -596,6 +609,20 @@ private function getObject(MockType $mockClass, string $type = '', bool $callOri } } + if ($callOriginalConstructor && $reflector->getConstructor() !== null) { + try { + $reflector->getConstructor()->invokeArgs($object, $arguments); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + } + return $object; } @@ -1003,44 +1030,6 @@ trait_exists($className, false)) { } } - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function instantiate(string $className, bool $callOriginalConstructor, array $arguments): object - { - if ($callOriginalConstructor) { - if (count($arguments) === 0) { - return new $className; - } - - try { - return (new ReflectionClass($className))->newInstanceArgs($arguments); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } - - try { - return (new ReflectionClass($className))->newInstanceWithoutConstructor(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - // @codeCoverageIgnoreEnd - } - } - /** * @psalm-param class-string $type * diff --git a/src/Runner/Version.php b/src/Runner/Version.php index 3386868a683..436629083f6 100644 --- a/src/Runner/Version.php +++ b/src/Runner/Version.php @@ -34,7 +34,7 @@ public static function id(): string } if (self::$version === '') { - self::$version = (new VersionId('11.2.0', dirname(__DIR__, 2)))->asString(); + self::$version = (new VersionId('11.2.1', dirname(__DIR__, 2)))->asString(); } return self::$version; diff --git a/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php b/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php index bb0ed6df1c1..c11ce1d5825 100644 --- a/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php +++ b/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php @@ -69,6 +69,10 @@ '11.0' => [ ReplaceRestrictDeprecationsWithIgnoreDeprecations::class, ], + + '11.1' => [ + RemoveCoverageElementCacheDirectoryAttribute::class, + ], ]; /** diff --git a/tests/_files/XmlConfigurationMigration/input-5859.xml b/tests/_files/XmlConfigurationMigration/input-5859.xml new file mode 100644 index 00000000000..ee91318f785 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/input-5859.xml @@ -0,0 +1,5 @@ + + + + diff --git a/tests/_files/XmlConfigurationMigration/output-5859.xml b/tests/_files/XmlConfigurationMigration/output-5859.xml new file mode 100644 index 00000000000..60ecbbc7fff --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/output-5859.xml @@ -0,0 +1,5 @@ + + + + diff --git a/tests/_files/mock-object/ClassCallingMethodInConstructor.php b/tests/_files/mock-object/ClassCallingMethodInConstructor.php new file mode 100644 index 00000000000..866d8eaa95e --- /dev/null +++ b/tests/_files/mock-object/ClassCallingMethodInConstructor.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class ClassCallingMethodInConstructor +{ + public function __construct() + { + $this->reset(); + } + + public function reset(): void + { + } + + public function second(): void + { + $this->reset(); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/MockBuilderTest.php b/tests/unit/Framework/MockObject/Creation/MockBuilderTest.php index 9897ce17a94..6e68f4a12eb 100644 --- a/tests/unit/Framework/MockObject/Creation/MockBuilderTest.php +++ b/tests/unit/Framework/MockObject/Creation/MockBuilderTest.php @@ -27,6 +27,7 @@ use PHPUnit\Framework\MockObject\Generator\NameAlreadyInUseException; use PHPUnit\Framework\TestCase; use PHPUnit\TestFixture\MockObject\AbstractClass; +use PHPUnit\TestFixture\MockObject\ClassCallingMethodInConstructor; use PHPUnit\TestFixture\MockObject\ExtendableClass; use PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration; use PHPUnit\TestFixture\MockObject\TraitWithConcreteAndAbstractMethod; @@ -165,4 +166,16 @@ public function testCreatesMockObjectForUnknownType(): void $this->assertInstanceOf(MockObject::class, $double); } + + #[TestDox('Mocked methods can be called from the original constructor of a partially mocked class')] + public function testOnlyMethodCalledInConstructorWorks(): void + { + $double = $this->getMockBuilder(ClassCallingMethodInConstructor::class) + ->onlyMethods(['reset']) + ->getMock(); + + $double->expects($this->once())->method('reset'); + + $double->second(); + } } diff --git a/tests/unit/Framework/MockObject/TestDoubleTestCase.php b/tests/unit/Framework/MockObject/TestDoubleTestCase.php index 1284f7f96bb..06938750381 100644 --- a/tests/unit/Framework/MockObject/TestDoubleTestCase.php +++ b/tests/unit/Framework/MockObject/TestDoubleTestCase.php @@ -281,6 +281,18 @@ final public function testOriginalCloneMethodCanOptionallyBeCalledWhenTestDouble clone $double; } + public function testMethodNameCanOnlyBeConfiguredOnce(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $this->expectException(MethodNameAlreadyConfiguredException::class); + + $double + ->method('doSomething') + ->method('doSomething') + ->willReturn(true); + } + /** * @psalm-template RealInstanceType of object * diff --git a/tests/unit/TextUI/Configuration/Xml/MigratorTest.php b/tests/unit/TextUI/Configuration/Xml/MigratorTest.php index b05e1e0bccd..f8ce7a25051 100644 --- a/tests/unit/TextUI/Configuration/Xml/MigratorTest.php +++ b/tests/unit/TextUI/Configuration/Xml/MigratorTest.php @@ -10,6 +10,7 @@ namespace PHPUnit\TextUI\XmlConfiguration; use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\Ticket; use PHPUnit\Framework\TestCase; use PHPUnit\Util\Xml\Loader as XmlLoader; @@ -40,4 +41,18 @@ public function testCanMigratePhpUnit95Configuration(): void ), ); } + + #[TestDox('Remove cacheDirectory attribute from element when migrating from PHPUnit 11.1 to PHPUnit 11.2')] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/5859')] + public function testIssue5859(): void + { + $this->assertEquals( + (new XmlLoader)->loadFile(__DIR__ . '/../../../../_files/XmlConfigurationMigration/output-5859.xml'), + (new XmlLoader)->load( + (new Migrator)->migrate( + __DIR__ . '/../../../../_files/XmlConfigurationMigration/input-5859.xml', + ), + ), + ); + } }