diff --git a/src/Framework/MockObject/Matcher/Parameters.php b/src/Framework/MockObject/Matcher/Parameters.php index 7b524e4d08e..7745c7aca5b 100644 --- a/src/Framework/MockObject/Matcher/Parameters.php +++ b/src/Framework/MockObject/Matcher/Parameters.php @@ -29,6 +29,11 @@ class PHPUnit_Framework_MockObject_Matcher_Parameters extends PHPUnit_Framework_ */ protected $invocation; + /** + * @var PHPUnit_Framework_ExpectationFailedException + */ + private $parameterVerificationResult; + /** * @param array $parameters */ @@ -71,8 +76,11 @@ public function toString() public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->invocation = $invocation; - - return $this->verify(); + try { + return $this->parameterVerificationResult = $this->verify(); + } catch (PHPUnit_Framework_ExpectationFailedException $e) { + throw $this->parameterVerificationResult = $e; + } } /** @@ -90,6 +98,10 @@ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) */ public function verify() { + if (isset($this->parameterVerificationResult)) { + return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); + } + if ($this->invocation === null) { throw new PHPUnit_Framework_ExpectationFailedException( 'Mocked method does not exist.' @@ -127,4 +139,16 @@ public function verify() return true; } + + /** + * @return bool + * @throws PHPUnit_Framework_ExpectationFailedException + */ + private function guardAgainstDuplicateEvaluationOfParameterConstraints() + { + if ($this->parameterVerificationResult instanceof Exception) { + throw $this->parameterVerificationResult; + } + return (bool) $this->parameterVerificationResult; + } } diff --git a/tests/MockObjectTest.php b/tests/MockObjectTest.php index b5f66e1dd21..17334ec033c 100644 --- a/tests/MockObjectTest.php +++ b/tests/MockObjectTest.php @@ -1017,4 +1017,20 @@ public function traversableProvider() [['\Iterator','\Traversable']] ]; } + + public function testParameterCallbackConstraintOnlyEvaluatedOnce() + { + $mock = $this->getMockBuilder(Foo::class)->setMethods(['bar'])->getMock(); + $expectedNumberOfCalls = 1; + $callCount = 0; + + $mock->expects($this->exactly($expectedNumberOfCalls))->method('bar') + ->with($this->callback(function ($argument) use (&$callCount) { + return $argument === 'call_' . $callCount++; + })); + + for ($i = 0; $i < $expectedNumberOfCalls; $i++) { + $mock->bar('call_' . $i); + } + } }