Skip to content

Commit

Permalink
Fix issue #120 by delaying unexpected method call evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
Géza Búza committed Sep 2, 2019
1 parent 93d6a38 commit 3c22ce0
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 25 deletions.
39 changes: 22 additions & 17 deletions spec/Prophecy/Call/CallCenterSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace spec\Prophecy\Call;

use PhpSpec\ObjectBehavior;
use Prophecy\Exception\Call\UnexpectedCallException;
use Prophecy\Promise\PromiseInterface;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Prophecy\ObjectProphecy;
Expand Down Expand Up @@ -123,23 +124,6 @@ function it_executes_promise_of_method_prophecy_that_matches_with_highest_score_
->shouldReturn('second');
}

function it_throws_exception_if_call_does_not_match_any_of_defined_method_prophecies(
$objectProphecy,
MethodProphecy $method,
ArgumentsWildcard $arguments
) {
$method->getMethodName()->willReturn('getName');
$method->getArgumentsWildcard()->willReturn($arguments);
$arguments->scoreArguments(array('world', 'everything'))->willReturn(false);
$arguments->__toString()->willReturn('arg1, arg2');

$objectProphecy->getMethodProphecies()->willReturn(array('method1' => array($method)));
$objectProphecy->getMethodProphecies('getName')->willReturn(array($method));

$this->shouldThrow('Prophecy\Exception\Call\UnexpectedCallException')
->duringMakeCall($objectProphecy, 'getName', array('world', 'everything'));
}

function it_returns_null_if_method_prophecy_that_matches_makeCall_arguments_has_no_promise(
$objectProphecy,
MethodProphecy $method,
Expand Down Expand Up @@ -177,4 +161,25 @@ function it_finds_recorded_calls_by_a_method_name_and_arguments_wildcard(
$calls[0]->getMethodName()->shouldReturn('getName');
$calls[0]->getArguments()->shouldReturn(array('everything'));
}

function it_records_the_error_when_stub_has_got_unexpected_method_calls(
$objectProphecy,
MethodProphecy $method,
ArgumentsWildcard $arguments
) {
$method->getMethodName()->willReturn('getName');
$method->getArgumentsWildcard()->willReturn($arguments);

$arguments->getTokens()->willReturn(array());

$objectProphecy->getMethodProphecies()->willReturn(array('getName' => array($method)));
$objectProphecy->getMethodProphecies('getName')->willReturn(array($method));
$objectProphecy->getMethodProphecies('method1')->willReturn(array());
$objectProphecy->reveal()->willReturn(new \stdClass());

$this->shouldNotThrow('Prophecy\Exception\Call\UnexpectedCallException')
->duringMakeCall($objectProphecy, 'method1', array());

$this->shouldThrow('Prophecy\Exception\Call\UnexpectedCallException')->duringCheckUnexpectedCalls();
}
}
3 changes: 2 additions & 1 deletion spec/Prophecy/Prophecy/MethodProphecySpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ function it_records_even_failed_checked_predictions(
$this->withArguments($arguments);

try {
$this->callOnWrappedObject('shouldHave', array($prediction));
$this->callOnWrappedObject('shouldHave', array($prediction));
} catch (\Exception $e) {}

$this->getCheckedPredictions()->shouldReturn(array($prediction));
Expand All @@ -342,6 +342,7 @@ function it_checks_prediction_via_shouldHave_method_call_with_callback(
Call $call1,
Call $call2
) {
$objectProphecy->addMethodProphecy($this)->willReturn(null);
$callback = function ($calls, $object, $method) {
throw new RuntimeException;
};
Expand Down
11 changes: 11 additions & 0 deletions spec/Prophecy/Prophecy/ObjectProphecySpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,17 @@ function it_returns_new_MethodProphecy_for_all_callback_signatures(

$methodProphecy2->shouldNotBe($methodProphecy1);
}

function it_throws_UnexpectedCallException_during_checkPredictions_if_unexpected_method_was_called(
$lazyDouble, CallCenter $callCenter
) {
$this->beConstructedWith($lazyDouble, $callCenter);

$callCenter->checkUnexpectedCalls()->willThrow('Prophecy\Exception\Call\UnexpectedCallException');

$this->shouldThrow('Prophecy\Exception\Call\UnexpectedCallException')
->duringCheckProphecyMethodsPredictions();
}
}

class ObjectProphecySpecFixtureA
Expand Down
54 changes: 47 additions & 7 deletions src/Prophecy/Call/CallCenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Util\StringUtil;
use Prophecy\Exception\Call\UnexpectedCallException;
use SplObjectStorage;

/**
* Calls receiver & manager.
Expand All @@ -32,6 +33,11 @@ class CallCenter
*/
private $recordedCalls = array();

/**
* @var SplObjectStorage
*/
private $unexpectedCalls;

/**
* Initializes call center.
*
Expand All @@ -40,6 +46,7 @@ class CallCenter
public function __construct(StringUtil $util = null)
{
$this->util = $util ?: new StringUtil;
$this->unexpectedCalls = new SplObjectStorage();
}

/**
Expand Down Expand Up @@ -81,16 +88,14 @@ public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments
}

// There are method prophecies, so it's a fake/stub. Searching prophecy for this call
$matches = array();
foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
$matches[] = array($score, $methodProphecy);
}
}
$matches = $this->findMethodProphecies($prophecy, $methodName, $arguments);

// If fake/stub doesn't have method prophecy for this call - throw exception
if (!count($matches)) {
throw $this->createUnexpectedCallException($prophecy, $methodName, $arguments);
$this->unexpectedCalls->attach(new Call($methodName, $arguments, null, null, $file, $line), $prophecy);
$this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);

return null;
}

// Sort matches by their score value
Expand Down Expand Up @@ -147,6 +152,22 @@ public function findCalls($methodName, ArgumentsWildcard $wildcard)
);
}

/**
* @throws UnexpectedCallException
*/
public function checkUnexpectedCalls()
{
/** @var Call $call */
foreach ($this->unexpectedCalls as $call) {
$prophecy = $this->unexpectedCalls[$call];

// If fake/stub doesn't have method prophecy for this call - throw exception
if (!count($this->findMethodProphecies($prophecy, $call->getMethodName(), $call->getArguments()))) {
throw $this->createUnexpectedCallException($prophecy, $call->getMethodName(), $call->getArguments());
}
}
}

private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName,
array $arguments)
{
Expand Down Expand Up @@ -226,4 +247,23 @@ function () use ($indentationLength) {
$arguments
);
}

/**
* @param ObjectProphecy $prophecy
* @param string $methodName
* @param array $arguments
*
* @return array
*/
private function findMethodProphecies(ObjectProphecy $prophecy, $methodName, array $arguments)
{
$matches = array();
foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
$matches[] = [$score, $methodProphecy];
}
}

return $matches;
}
}
3 changes: 3 additions & 0 deletions src/Prophecy/Prophecy/ObjectProphecy.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,15 @@ public function findProphecyMethodCalls($methodName, ArgumentsWildcard $wildcard
* Checks that registered method predictions do not fail.
*
* @throws \Prophecy\Exception\Prediction\AggregateException If any of registered predictions fail
* @throws \Prophecy\Exception\Call\UnexpectedCallException
*/
public function checkProphecyMethodsPredictions()
{
$exception = new AggregateException(sprintf("%s:\n", get_class($this->reveal())));
$exception->setObjectProphecy($this);

$this->callCenter->checkUnexpectedCalls();

foreach ($this->methodProphecies as $prophecies) {
foreach ($prophecies as $prophecy) {
try {
Expand Down

0 comments on commit 3c22ce0

Please sign in to comment.