diff --git a/src/Framework/MockObject/Generator.php b/src/Framework/MockObject/Generator.php index c7300f761b1..6285241764a 100644 --- a/src/Framework/MockObject/Generator.php +++ b/src/Framework/MockObject/Generator.php @@ -67,14 +67,11 @@ class Generator * @param bool $allowMockingUnknownTypes * @param bool $returnValueGeneration * - * @throws Exception - * @throws RuntimeException - * @throws \PHPUnit\Framework\Exception * @throws \ReflectionException * * @return MockObject */ - public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true, $returnValueGeneration = true) + public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true, $returnValueGeneration = true, array $methodsExcepted = []) { if (!\is_array($type) && !\is_string($type)) { throw InvalidArgumentHelper::factory(1, 'array or string'); @@ -185,7 +182,8 @@ function ($type) { $callOriginalClone, $callAutoload, $cloneArguments, - $callOriginalMethods + $callOriginalMethods, + $methodsExcepted ); return $this->getObject( @@ -220,7 +218,7 @@ function ($type) { * * @return MockObject */ - public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) + public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true, array $methodsExcept = []) { if (!\is_string($originalClassName)) { throw InvalidArgumentHelper::factory(1, 'string'); @@ -253,7 +251,12 @@ public function getMockForAbstractClass($originalClassName, array $arguments = [ $callOriginalConstructor, $callOriginalClone, $callAutoload, - $cloneArguments + $cloneArguments, + false, + null, + true, + true, + $methodsExcept ); } @@ -281,7 +284,7 @@ public function getMockForAbstractClass($originalClassName, array $arguments = [ * * @return MockObject */ - public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) + public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true, array $methodsExcept = []) { if (!\is_string($traitName)) { throw InvalidArgumentHelper::factory(1, 'string'); @@ -321,7 +324,7 @@ public function getMockForTrait($traitName, array $arguments = [], $mockClassNam $className['className'] ); - return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); + return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments, $methodsExcept); } /** @@ -398,7 +401,7 @@ public function getObjectForTrait($traitName, array $arguments = [], $traitClass * * @return array */ - public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) + public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, array $methodsExcepted = []) { if (\is_array($type)) { \sort($type); @@ -412,12 +415,14 @@ public function generate($type, array $methods = null, $mockClassName = '', $cal $callOriginalClone, $callAutoload, $cloneArguments, - $callOriginalMethods + $callOriginalMethods, + $methodsExcepted ); } $key = \md5( \is_array($type) ? \implode('_', $type) : $type . \serialize($methods) . + \serialize($methodsExcepted) . \serialize($callOriginalClone) . \serialize($cloneArguments) . \serialize($callOriginalMethods) @@ -431,7 +436,8 @@ public function generate($type, array $methods = null, $mockClassName = '', $cal $callOriginalClone, $callAutoload, $cloneArguments, - $callOriginalMethods + $callOriginalMethods, + $methodsExcepted ); } @@ -548,12 +554,16 @@ public function getClassMethods($className): array * * @return MockMethod[] */ - public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments): array + public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments, array $methodsExcepted = []): array { $class = new ReflectionClass($className); $methods = []; foreach ($class->getMethods() as $method) { + if ($methodsExcepted !== [] && \in_array($method->getName(), $methodsExcepted)) { + continue; + } + if (($method->isPublic() || $method->isAbstract()) && $this->canMockMethod($method)) { $methods[] = MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments); } @@ -655,6 +665,7 @@ private function evalClass($code, $className): void * @param bool $callAutoload * @param bool $cloneArguments * @param bool $callOriginalMethods + * @param array $methodsExcepted * * @throws \InvalidArgumentException * @throws \ReflectionException @@ -662,7 +673,7 @@ private function evalClass($code, $className): void * * @return array */ - private function generateMock($type, $explicitMethods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) + private function generateMock($type, $explicitMethods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods, $methodsExcepted) { $classTemplate = $this->getTemplate('mocked_class.tpl'); @@ -830,7 +841,7 @@ private function generateMock($type, $explicitMethods, $mockClassName, $callOrig if ($explicitMethods === [] && ($isClass || $isInterface)) { $mockMethods->addMethods( - ...$this->mockClassMethods($mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments) + ...$this->mockClassMethods($mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments, $methodsExcepted) ); } diff --git a/src/Framework/MockObject/MockBuilder.php b/src/Framework/MockObject/MockBuilder.php index 3199afd6db4..8f060b7d1dd 100644 --- a/src/Framework/MockObject/MockBuilder.php +++ b/src/Framework/MockObject/MockBuilder.php @@ -120,7 +120,8 @@ public function getMock() $this->callOriginalMethods, $this->proxyTarget, $this->allowMockingUnknownTypes, - $this->returnValueGeneration + $this->returnValueGeneration, + $this->methodsExcept ); $this->testCase->registerMockObject($object); @@ -143,7 +144,8 @@ public function getMockForAbstractClass() $this->originalClone, $this->autoload, $this->methods, - $this->cloneArguments + $this->cloneArguments, + $this->methodsExcept ); $this->testCase->registerMockObject($object); @@ -166,7 +168,8 @@ public function getMockForTrait() $this->originalClone, $this->autoload, $this->methods, - $this->cloneArguments + $this->cloneArguments, + $this->methodsExcept ); $this->testCase->registerMockObject($object); diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index 82f702001a4..ebe5d5eaed5 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -1498,7 +1498,7 @@ protected function getMockFromWsdl($wsdlFile, $originalClassName = '', $mockClas * @throws ReflectionException * @throws \InvalidArgumentException */ - protected function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false): MockObject + protected function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false, array $methodsExcept = []): MockObject { $mockObject = $this->getMockObjectGenerator()->getMockForTrait( $traitName, @@ -1508,7 +1508,8 @@ protected function getMockForTrait($traitName, array $arguments = [], $mockClass $callOriginalClone, $callAutoload, $mockedMethods, - $cloneArguments + $cloneArguments, + $methodsExcept ); $this->registerMockObject($mockObject); diff --git a/tests/_files/ClassWithSinglePublicMethod.php b/tests/_files/ClassWithSinglePublicMethod.php new file mode 100644 index 00000000000..0b4aabc73aa --- /dev/null +++ b/tests/_files/ClassWithSinglePublicMethod.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * A class with only one public method + */ +class ClassWithSinglePublicMethod +{ + public function falseReturn() + { + return false; + } +} diff --git a/tests/unit/Framework/MockObject/MockBuilderTest.php b/tests/unit/Framework/MockObject/MockBuilderTest.php index 5d9e3c9fa63..707627eaaf9 100644 --- a/tests/unit/Framework/MockObject/MockBuilderTest.php +++ b/tests/unit/Framework/MockObject/MockBuilderTest.php @@ -126,4 +126,13 @@ public function testProvidesAFluentInterface(): void $this->assertInstanceOf(MockBuilder::class, $spec); } + + public function testExceptMethodFromClassWithSinglePublicMethod(): void + { + $spec = $this->getMockBuilder(ClassWithSinglePublicMethod::class) + ->setMethodsExcept(['falseReturn']) + ->getMock(); + + $this->assertFalse($spec->falseReturn()); + } }