From e860ff44754ba9e52df3ecf76220ea0a970de345 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Mon, 1 Jun 2020 10:14:39 +0200 Subject: [PATCH] Throw exception on non unique mocked method This patch will make a mock throw an exception when multiple matchers can be applied to a invoke. When allowing this, results of tests are not predictable. refs #4255 --- .../MockObject/InvocationHandler.php | 48 ++++++++++--------- .../MockObject/InvocationHandlerTest.php | 35 ++++++++++++++ 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/Framework/MockObject/InvocationHandler.php b/src/Framework/MockObject/InvocationHandler.php index 7b2b2bb7493..ffad6c0c881 100644 --- a/src/Framework/MockObject/InvocationHandler.php +++ b/src/Framework/MockObject/InvocationHandler.php @@ -113,30 +113,11 @@ public function expects(InvocationOrder $rule): InvocationMocker public function invoke(Invocation $invocation) { $exception = null; - $hasReturnValue = false; $returnValue = null; + $match = $this->findMatcher($invocation); - foreach ($this->matchers as $match) { - try { - if ($match->matches($invocation)) { - $value = $match->invoked($invocation); - - if (!$hasReturnValue) { - $returnValue = $value; - $hasReturnValue = true; - } - } - } catch (\Exception $e) { - $exception = $e; - } - } - - if ($exception !== null) { - throw $exception; - } - - if ($hasReturnValue) { - return $returnValue; + if ($match !== null) { + return $match->invoked($invocation); } if (!$this->returnValueGeneration) { @@ -186,6 +167,29 @@ public function verify(): void } } + private function findMatcher(Invocation $invocation): ?Matcher + { + $result = []; + + foreach ($this->matchers as $matcher) { + if ($matcher->matches($invocation)) { + $result[] = $matcher; + } + } + + if (\count($result) > 1) { + throw new ExpectationFailedException( + \sprintf( + 'Non unique mocked method invocation: %s::%s', + $invocation->getClassName(), + $invocation->getMethodName() + ) + ); + } + + return \current($result) ?: null; + } + private function addMatcher(Matcher $matcher): void { $this->matchers[] = $matcher; diff --git a/tests/unit/Framework/MockObject/InvocationHandlerTest.php b/tests/unit/Framework/MockObject/InvocationHandlerTest.php index 59e16b6734a..4842f541b5a 100644 --- a/tests/unit/Framework/MockObject/InvocationHandlerTest.php +++ b/tests/unit/Framework/MockObject/InvocationHandlerTest.php @@ -9,6 +9,7 @@ */ namespace PHPUnit\Framework\MockObject; +use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; class InvocationHandlerTest extends TestCase @@ -23,4 +24,38 @@ public function testExceptionThrownIn__ToStringIsDeferred(): void $this->expectExceptionMessage('planned error'); $mock->__toString(); } + + public function testSingleMatcherIsHandled(): void + { + $mock = $this->getMockBuilder(\stdClass::class) + ->addMethods(['foo']) + ->getMock(); + + $mock->expects($this->once()) + ->method('foo') + ->willReturn('result'); + + $this->assertSame('result', $mock->foo()); + } + + public function testNonUniqueMatchThrowsException(): void + { + $mock = $this->getMockBuilder(\stdClass::class) + ->addMethods(['foo']) + ->getMock(); + + $mock->expects($this->any()) + ->method($this->stringStartsWith('foo')) + ->willReturn('result'); + + $mock->expects($this->any()) + ->method('foo') + ->with('bar') + ->willReturn('result'); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Non unique mocked method invocation: stdClass::foo'); + + $mock->foo(); + } }