From ab5b024aba7cd32b62a8aa9e9a3dbc66715553dc Mon Sep 17 00:00:00 2001 From: Sebastian Bergmann Date: Mon, 25 Nov 2019 17:29:57 +0100 Subject: [PATCH] Closes #3956 --- ChangeLog-9.0.md | 1 + src/Framework/MockObject/Generator.php | 161 +++--------------- src/Framework/TestCase.php | 77 ++++----- .../Framework/MockObject/GeneratorTest.php | 12 +- 4 files changed, 60 insertions(+), 191 deletions(-) diff --git a/ChangeLog-9.0.md b/ChangeLog-9.0.md index 29ab8be42e9..fb2a5ee5605 100644 --- a/ChangeLog-9.0.md +++ b/ChangeLog-9.0.md @@ -19,6 +19,7 @@ All notable changes of the PHPUnit 9.0 release series are documented in this fil * Implemented [#3495](https://github.com/sebastianbergmann/phpunit/issues/3495): Remove `assertArraySubset()` * Implemented [#3523](https://github.com/sebastianbergmann/phpunit/issues/3523): Remove the `setUseErrorHandler()` method * Implemented [#3951](https://github.com/sebastianbergmann/phpunit/issues/3951): Remove optional parameters of `assertFileEquals()` etc. +* Implemented [#3956](https://github.com/sebastianbergmann/phpunit/issues/3956): Remove support for doubling multiple interfaces * Implemented [#3957](https://github.com/sebastianbergmann/phpunit/issues/3957): Remove `expectExceptionMessageRegExp()` [9.0.0]: https://github.com/sebastianbergmann/phpunit/compare/8.5...master diff --git a/src/Framework/MockObject/Generator.php b/src/Framework/MockObject/Generator.php index b6052afedc2..4fe82370f7c 100644 --- a/src/Framework/MockObject/Generator.php +++ b/src/Framework/MockObject/Generator.php @@ -47,17 +47,12 @@ final class Generator /** * Returns a mock object for the specified class. * - * @param string|string[] $type - * @param null|array $methods + * @param null|array $methods * * @throws RuntimeException */ - public function getMock($type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false, object $proxyTarget = null, bool $allowMockingUnknownTypes = true, bool $returnValueGeneration = true): MockObject + public function getMock(string $type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false, object $proxyTarget = null, bool $allowMockingUnknownTypes = true, bool $returnValueGeneration = true): MockObject { - if (!\is_array($type) && !\is_string($type)) { - throw InvalidArgumentException::create(1, 'array or string'); - } - if (!\is_array($methods) && null !== $methods) { throw InvalidArgumentException::create(2, 'array'); } @@ -66,46 +61,15 @@ public function getMock($type, $methods = [], array $arguments = [], string $moc $type = 'Iterator'; } - if (\is_array($type)) { - $type = \array_unique( - \array_map( - static function ($type) { - if ($type === 'Traversable' || - $type === '\\Traversable' || - $type === '\\Iterator') { - return 'Iterator'; - } - - return $type; - }, + if (!$allowMockingUnknownTypes && !\class_exists($type, $callAutoload) && !\interface_exists($type, $callAutoload)) { + throw new RuntimeException( + \sprintf( + 'Cannot stub or mock class or interface "%s" which does not exist', $type ) ); } - if (!$allowMockingUnknownTypes) { - if (\is_array($type)) { - foreach ($type as $_type) { - if (!\class_exists($_type, $callAutoload) && - !\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $_type - ) - ); - } - } - } elseif (!\class_exists($type, $callAutoload) && !\interface_exists($type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $type - ) - ); - } - } - if (null !== $methods) { foreach ($methods as $method) { if (!\preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', (string) $method)) { @@ -316,12 +280,8 @@ public function getObjectForTrait(string $traitName, string $traitClassName = '' ); } - public function generate($type, array $methods = null, string $mockClassName = '', bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false): MockClass + public function generate(string $type, array $methods = null, string $mockClassName = '', bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false): MockClass { - if (\is_array($type)) { - \sort($type); - } - if ($mockClassName !== '') { return $this->generateMock( $type, @@ -335,7 +295,7 @@ public function generate($type, array $methods = null, string $mockClassName = ' } $key = \md5( - \is_array($type) ? \implode('_', $type) : $type . + $type . \serialize($methods) . \serialize($callOriginalClone) . \serialize($cloneArguments) . @@ -595,11 +555,9 @@ private function getObject(MockType $mockClass, $type = '', bool $callOriginalCo } /** - * @param array|string $type - * * @throws RuntimeException */ - private function generateMock($type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods): MockClass + private function generateMock(string $type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods): MockClass { $classTemplate = $this->getTemplate('mocked_class.tpl'); $additionalInterfaces = []; @@ -610,85 +568,23 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN $class = null; $mockMethods = new MockMethodSet; - if (\is_array($type)) { - $interfaceMethods = []; - - foreach ($type as $_type) { - if (!\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Interface "%s" does not exist.', - $_type - ) - ); - } - - $additionalInterfaces[] = $_type; - - try { - $typeClass = new \ReflectionClass($_type); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - foreach ($this->getClassMethods($_type) as $methodTrait) { - if (\in_array($methodTrait, $interfaceMethods, true)) { - throw new RuntimeException( - \sprintf( - 'Duplicate method "%s" not allowed.', - $methodTrait - ) - ); - } - - try { - $methodReflection = $typeClass->getMethod($methodTrait); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($this->canMockMethod($methodReflection)) { - $mockMethods->addMethods( - MockMethod::fromReflection($methodReflection, $callOriginalMethods, $cloneArguments) - ); - - $interfaceMethods[] = $methodTrait; - } - } - } - - unset($interfaceMethods); - } - - $mockClassName = $this->generateClassName( + $_mockClassName = $this->generateClassName( $type, $mockClassName, 'Mock_' ); - if (\class_exists($mockClassName['fullClassName'], $callAutoload)) { + if (\class_exists($_mockClassName['fullClassName'], $callAutoload)) { $isClass = true; - } elseif (\interface_exists($mockClassName['fullClassName'], $callAutoload)) { + } elseif (\interface_exists($_mockClassName['fullClassName'], $callAutoload)) { $isInterface = true; } if (!$isClass && !$isInterface) { - $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; + $prologue = 'class ' . $_mockClassName['originalClassName'] . "\n{\n}\n\n"; - if (!empty($mockClassName['namespaceName'])) { - $prologue = 'namespace ' . $mockClassName['namespaceName'] . + if (!empty($_mockClassName['namespaceName'])) { + $prologue = 'namespace ' . $_mockClassName['namespaceName'] . " {\n\n" . $prologue . "}\n\n" . "namespace {\n\n"; @@ -698,7 +594,7 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN $mockedCloneMethod = true; } else { try { - $class = new \ReflectionClass($mockClassName['fullClassName']); + $class = new \ReflectionClass($_mockClassName['fullClassName']); // @codeCoverageIgnoreStart } catch (\ReflectionException $e) { throw new RuntimeException( @@ -713,7 +609,7 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN throw new RuntimeException( \sprintf( 'Class "%s" is declared "final" and cannot be mocked.', - $mockClassName['fullClassName'] + $_mockClassName['fullClassName'] ) ); } @@ -736,7 +632,7 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN } // @codeCoverageIgnoreEnd - foreach ($this->getInterfaceOwnMethods($mockClassName['fullClassName']) as $methodTrait) { + foreach ($this->getInterfaceOwnMethods($_mockClassName['fullClassName']) as $methodTrait) { $methodName = $methodTrait->getName(); if ($class->hasMethod($methodName)) { @@ -762,7 +658,7 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN ); } - $mockClassName = $this->generateClassName( + $_mockClassName = $this->generateClassName( $actualClassName, '', 'Mock_' @@ -808,7 +704,7 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN if ($explicitMethods === [] && ($isClass || $isInterface)) { $mockMethods->addMethods( - ...$this->mockClassMethods($mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments) + ...$this->mockClassMethods($_mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments) ); } @@ -835,7 +731,7 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN } else { $mockMethods->addMethods( MockMethod::fromName( - $mockClassName['fullClassName'], + $_mockClassName['fullClassName'], $methodName, $cloneArguments ) @@ -873,12 +769,12 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN 'prologue' => $prologue ?? '', 'epilogue' => $epilogue ?? '', 'class_declaration' => $this->generateMockClassDeclaration( - $mockClassName, + $_mockClassName, $isInterface, $additionalInterfaces ), 'clone' => $cloneTrait, - 'mock_class_name' => $mockClassName['className'], + 'mock_class_name' => $_mockClassName['className'], 'mocked_methods' => $mockedMethods, 'method' => $methodTrait, ] @@ -886,20 +782,13 @@ private function generateMock($type, ?array $explicitMethods, string $mockClassN return new MockClass( $classTemplate->render(), - $mockClassName['className'], + $_mockClassName['className'], $configurable ); } - /** - * @param array|string $type - */ - private function generateClassName($type, string $className, string $prefix): array + private function generateClassName(string $type, string $className, string $prefix): array { - if (\is_array($type)) { - $type = \implode('_', $type); - } - if ($type[0] === '\\') { $type = \substr($type, 1); } diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index e980105c78f..13f3c0e546c 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -755,18 +755,12 @@ public function run(TestResult $result = null): TestResult /** * Returns a builder object to create mock objects using a fluent interface. * - * @param string|string[] $className - * * @psalm-template RealInstanceType of object - * @psalm-param class-string|string[] $className + * @psalm-param class-string $className * @psalm-return MockBuilder */ - public function getMockBuilder($className): MockBuilder + public function getMockBuilder(string $className): MockBuilder { - if (!\is_string($className)) { - $this->addWarning('Passing an array of interface names to getMockBuilder() for creating a test double that implements multiple interfaces is deprecated and will no longer be supported in PHPUnit 9.'); - } - $this->recordDoubledType($className); return new MockBuilder($this, $className); @@ -1548,18 +1542,12 @@ protected function createStub(string $originalClassName): Stub /** * Returns a mock object for the specified class. * - * @param string|string[] $originalClassName - * * @psalm-template RealInstanceType of object - * @psalm-param class-string|string[] $originalClassName + * @psalm-param class-string $originalClassName * @psalm-return MockObject&RealInstanceType */ - protected function createMock($originalClassName): MockObject + protected function createMock(string $originalClassName): MockObject { - if (!\is_string($originalClassName)) { - $this->addWarning('Passing an array of interface names to createMock() for creating a test double that implements multiple interfaces is deprecated and will no longer be supported in PHPUnit 9.'); - } - return $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() @@ -1571,13 +1559,11 @@ protected function createMock($originalClassName): MockObject /** * Returns a configured mock object for the specified class. * - * @param string|string[] $originalClassName - * * @psalm-template RealInstanceType of object - * @psalm-param class-string|string[] $originalClassName + * @psalm-param class-string $originalClassName * @psalm-return MockObject&RealInstanceType */ - protected function createConfiguredMock($originalClassName, array $configuration): MockObject + protected function createConfiguredMock(string $originalClassName, array $configuration): MockObject { $o = $this->createMock($originalClassName); @@ -1591,40 +1577,41 @@ protected function createConfiguredMock($originalClassName, array $configuration /** * Returns a partial mock object for the specified class. * - * @param string|string[] $originalClassName - * @param string[] $methods + * @param string[] $methods * * @psalm-template RealInstanceType of object - * @psalm-param class-string|string[] $originalClassName + * @psalm-param class-string $originalClassName * @psalm-return MockObject&RealInstanceType */ - protected function createPartialMock($originalClassName, array $methods): MockObject + protected function createPartialMock(string $originalClassName, array $methods): MockObject { - if (!\is_string($originalClassName)) { - $this->addWarning('Passing an array of interface names to createPartialMock() for creating a test double that implements multiple interfaces is deprecated and will no longer be supported in PHPUnit 9.'); + try { + $reflector = new \ReflectionClass($originalClassName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new Exception( + $e->getMessage(), + (int) $e->getCode(), + $e + ); } + // @codeCoverageIgnoreEnd - $class_names = \is_array($originalClassName) ? $originalClassName : [$originalClassName]; - - foreach ($class_names as $class_name) { - $reflection = new \ReflectionClass($class_name); + $mockedMethodsThatDontExist = \array_filter( + $methods, + static function (string $method) use ($reflector) { + return !$reflector->hasMethod($method); + } + ); - $mockedMethodsThatDontExist = \array_filter( - $methods, - static function (string $method) use ($reflection) { - return !$reflection->hasMethod($method); - } + if ($mockedMethodsThatDontExist) { + $this->addWarning( + \sprintf( + 'createPartialMock() called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', + \implode(', ', $mockedMethodsThatDontExist), + $originalClassName + ) ); - - if ($mockedMethodsThatDontExist) { - $this->addWarning( - \sprintf( - 'createPartialMock called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', - \implode(', ', $mockedMethodsThatDontExist), - $class_name - ) - ); - } } return $this->getMockBuilder($originalClassName) diff --git a/tests/unit/Framework/MockObject/GeneratorTest.php b/tests/unit/Framework/MockObject/GeneratorTest.php index 601f305946f..ab66df66b6e 100644 --- a/tests/unit/Framework/MockObject/GeneratorTest.php +++ b/tests/unit/Framework/MockObject/GeneratorTest.php @@ -45,13 +45,6 @@ public function testGetMockThrowsExceptionWhenInvalidFunctionNameIsPassedInAsAFu $this->generator->getMock(stdClass::class, [0]); } - public function testGetMockThrowsExceptionWithInvalidClassArgumentType(): void - { - $this->expectException(\PHPUnit\Framework\InvalidArgumentException::class); - - $this->generator->getMock(false); - } - public function testGetMockThrowsExceptionWithInvalidMethods(): void { $this->expectException(\PHPUnit\Framework\InvalidArgumentException::class); @@ -74,7 +67,7 @@ public function testGetMockThrowsExceptionWithNonExistingClasses(): void $this->assertFalse(\class_exists('Tux')); - $this->generator->getMock(['Tux', false], [], [], '', true, true, false, true, false, null, false); + $this->generator->getMock('Tux', [], [], '', true, true, false, true, false, null, false); } public function testGetMockThrowsExceptionWithExistingClassAsMockName(): void @@ -218,10 +211,9 @@ public function testExceptionIsRaisedForMutuallyExclusiveOptions(): void public function testCanImplementInterfacesThatHaveMethodsWithReturnTypes(): void { - $stub = $this->generator->getMock([AnInterfaceWithReturnType::class, AnInterface::class]); + $stub = $this->generator->getMock(AnInterfaceWithReturnType::class); $this->assertInstanceOf(AnInterfaceWithReturnType::class, $stub); - $this->assertInstanceOf(AnInterface::class, $stub); $this->assertInstanceOf(MockObject::class, $stub); }