Permalink
Browse files

Merge pull request #283 from davedevelopment/spies

Basic/naive spy implementation
  • Loading branch information...
davedevelopment committed Oct 7, 2014
2 parents 627a6c8 + 31866ac commit aab7d1ccfa1caf4c22bd8d77947b28b48fc9c954
@@ -1,5 +1,9 @@
# Change Log
## 0.9.3 (XXXX-XX-XX)
* Added a basic spy implementation
## 0.9.2 (2014-09-03)
* Some workarounds for the serilisation problems created by changes to PHP in 5.5.13, 5.4.29,
@@ -75,8 +75,15 @@ public static function mock()
}
/**
* Another shortcut to \Mockery\Container:mock().
*
* @return \Mockery\MockInterface
*/
public static function spy()
{
$args = func_get_args();
return call_user_func_array(array(self::getContainer(), 'mock'), $args)->shouldIgnoreMissing();
}
/**
* @return \Mockery\MockInterface
*/
public static function instanceMock()
@@ -693,4 +693,9 @@ public function __clone()
$this->_countValidators = $newValidators;
}
public function getName()
{
return $this->_name;
}
}
@@ -73,7 +73,7 @@ protected function appendToClass($class, $code)
private function renderMethodBody($method, $config)
{
$invoke = $method->isStatic() ? 'static::__callStatic' : '$this->__call';
$invoke = $method->isStatic() ? 'static::_mockery_handleStaticMethodCall' : '$this->_mockery_handleMethodCall';
$body = <<<BODY
{
\$argc = func_num_args();
@@ -0,0 +1,25 @@
<?php
namespace Mockery;
class MethodCall
{
private $method;
private $args;
public function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
}
public function getMethod()
{
return $this->method;
}
public function getArgs()
{
return $this->args;
}
}
@@ -137,6 +137,8 @@ class Mock implements MockInterface
protected $_mockery_allowMockingProtectedMethods = false;
protected $_mockery_receivedMethodCalls;
/**
* If shouldIgnoreMissing is called, this value will be returned on all calls to missing methods
* @var mixed
@@ -312,68 +314,12 @@ public function byDefault()
*/
public function __call($method, array $args)
{
$rm = $this->mockery_getMethod($method);
if ($rm && $rm->isProtected() && !$this->_mockery_allowMockingProtectedMethods) {
if ($rm->isAbstract()) {
return;
}
try {
$prototype = $rm->getPrototype();
if ($prototype->isAbstract()) {
return;
}
} catch (\ReflectionException $re) {
// noop - there is no hasPrototype method
}
return call_user_func_array("parent::$method", $args);
}
if (isset($this->_mockery_expectations[$method])
&& !$this->_mockery_disableExpectationMatching) {
$handler = $this->_mockery_expectations[$method];
try {
return $handler->call($args);
} catch (\Mockery\Exception\NoMatchingExpectationException $e) {
if (!$this->_mockery_ignoreMissing && !$this->_mockery_deferMissing) {
throw $e;
}
}
}
if (!is_null($this->_mockery_partial) && method_exists($this->_mockery_partial, $method)) {
return call_user_func_array(array($this->_mockery_partial, $method), $args);
} elseif ($this->_mockery_deferMissing && is_callable("parent::$method")) {
return call_user_func_array("parent::$method", $args);
} elseif ($method == '__toString') {
// __toString is special because we force its addition to the class API regardless of the
// original implementation. Thus, we should always return a string rather than honor
// _mockery_ignoreMissing and break the API with an error.
return sprintf("%s#%s", __CLASS__, spl_object_hash($this));
} elseif ($this->_mockery_ignoreMissing) {
if($this->_mockery_defaultReturnValue instanceof \Mockery\Undefined)
return call_user_func_array(array($this->_mockery_defaultReturnValue, $method), $args);
else
return $this->_mockery_defaultReturnValue;
}
throw new \BadMethodCallException(
'Method ' . __CLASS__ . '::' . $method . '() does not exist on this mock object'
);
return $this->_mockery_handleMethodCall($method, $args);
}
public static function __callStatic($method, array $args)
{
try {
$associatedRealObject = \Mockery::fetchMock(__CLASS__);
return $associatedRealObject->__call($method, $args);
} catch (\BadMethodCallException $e) {
throw new \BadMethodCallException(
'Static method ' . $associatedRealObject->mockery_getName() . '::' . $method
. '() does not exist on this mock object'
);
}
return self::_mockery_handleStaticMethodCall($method, $args);
}
/**
@@ -656,6 +602,103 @@ public function mockery_getMethod($name)
return null;
}
public function shouldHaveReceived($method, $args = null)
{
$expectation = new \Mockery\VerificationExpectation($this, $method);
if (null !== $args) {
$expectation->withArgs($args);
}
$expectation->atLeast()->once();
$director = new \Mockery\VerificationDirector($this->_mockery_getReceivedMethodCalls(), $expectation);
$director->verify();
return $director;
}
public function shouldNotHaveReceived($method, $args = null)
{
$expectation = new \Mockery\VerificationExpectation($this, $method);
if (null !== $args) {
$expectation->withArgs($args);
}
$expectation->never();
$director = new \Mockery\VerificationDirector($this->_mockery_getReceivedMethodCalls(), $expectation);
$director->verify();
return $director;
}
protected static function _mockery_handleStaticMethodCall($method, array $args)
{
try {
$associatedRealObject = \Mockery::fetchMock(__CLASS__);
return $associatedRealObject->__call($method, $args);
} catch (\BadMethodCallException $e) {
throw new \BadMethodCallException(
'Static method ' . $associatedRealObject->mockery_getName() . '::' . $method
. '() does not exist on this mock object'
);
}
}
protected function _mockery_getReceivedMethodCalls()
{
return $this->_mockery_receivedMethodCalls ?: $this->_mockery_receivedMethodCalls = new \Mockery\ReceivedMethodCalls();
}
protected function _mockery_handleMethodCall($method, array $args)
{
$this->_mockery_getReceivedMethodCalls()->push(new \Mockery\MethodCall($method, $args));
$rm = $this->mockery_getMethod($method);
if ($rm && $rm->isProtected() && !$this->_mockery_allowMockingProtectedMethods) {
if ($rm->isAbstract()) {
return;
}
try {
$prototype = $rm->getPrototype();
if ($prototype->isAbstract()) {
return;
}
} catch (\ReflectionException $re) {
// noop - there is no hasPrototype method
}
return call_user_func_array("parent::$method", $args);
}
if (isset($this->_mockery_expectations[$method])
&& !$this->_mockery_disableExpectationMatching) {
$handler = $this->_mockery_expectations[$method];
try {
return $handler->call($args);
} catch (\Mockery\Exception\NoMatchingExpectationException $e) {
if (!$this->_mockery_ignoreMissing && !$this->_mockery_deferMissing) {
throw $e;
}
}
}
if (!is_null($this->_mockery_partial) && method_exists($this->_mockery_partial, $method)) {
return call_user_func_array(array($this->_mockery_partial, $method), $args);
} elseif ($this->_mockery_deferMissing && is_callable("parent::$method")) {
return call_user_func_array("parent::$method", $args);
} elseif ($method == '__toString') {
// __toString is special because we force its addition to the class API regardless of the
// original implementation. Thus, we should always return a string rather than honor
// _mockery_ignoreMissing and break the API with an error.
return sprintf("%s#%s", __CLASS__, spl_object_hash($this));
} elseif ($this->_mockery_ignoreMissing) {
if ($this->_mockery_defaultReturnValue instanceof \Mockery\Undefined)
return call_user_func_array(array($this->_mockery_defaultReturnValue, $method), $args);
else
return $this->_mockery_defaultReturnValue;
}
throw new \BadMethodCallException(
'Method ' . __CLASS__ . '::' . $method . '() does not exist on this mock object'
);
}
protected function mockery_getMethods()
{
if (static::$_mockery_methods) {
@@ -0,0 +1,30 @@
<?php
namespace Mockery;
class ReceivedMethodCalls
{
private $methodCalls = array();
public function push(MethodCall $methodCall)
{
$this->methodCalls[] = $methodCall;
}
public function verify(Expectation $expectation)
{
foreach ($this->methodCalls as $methodCall) {
if ($methodCall->getMethod() !== $expectation->getName()) {
continue;
}
if (!$expectation->matchArgs($methodCall->getArgs())) {
continue;
}
$expectation->verifyCall($methodCall->getArgs());
}
$expectation->verify();
}
}
@@ -0,0 +1,89 @@
<?php
namespace Mockery;
class VerificationDirector
{
private $receivedMethodCalls;
private $expectation;
public function __construct(ReceivedMethodCalls $receivedMethodCalls, VerificationExpectation $expectation)
{
$this->receivedMethodCalls = $receivedMethodCalls;
$this->expectation = $expectation;
}
public function verify()
{
return $this->receivedMethodCalls->verify($this->expectation);
}
public function with()
{
return $this->cloneApplyAndVerify("with", func_get_args());
}
public function withArgs(array $args)
{
return $this->cloneApplyAndVerify("withArgs", array($args));
}
public function withNoArgs()
{
return $this->cloneApplyAndVerify("withNoArgs", array());
}
public function withAnyArgs()
{
return $this->cloneApplyAndVerify("withAnyArgs", array());
}
public function times($limit = null)
{
return $this->cloneWithoutCountValidatorsApplyAndVerify("times", array($limit));
}
public function once()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify("once", array());
}
public function twice()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify("twice", array());
}
public function atLeast()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify("atLeast", array());
}
public function atMost()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify("atMost", array());
}
public function between($minimum, $maximum)
{
return $this->cloneWithoutCountValidatorsApplyAndVerify("between", array($minimum, $maximum));
}
protected function cloneWithoutCountValidatorsApplyAndVerify($method, $args)
{
$expectation = clone $this->expectation;
$expectation->clearCountValidators();
call_user_func_array(array($expectation, $method), $args);
$director = new VerificationDirector($this->receivedMethodCalls, $expectation);
$director->verify();
return $director;
}
protected function cloneApplyAndVerify($method, $args)
{
$expectation = clone $this->expectation;
call_user_func_array(array($expectation, $method), $args);
$director = new VerificationDirector($this->receivedMethodCalls, $expectation);
$director->verify();
return $director;
}
}
@@ -0,0 +1,17 @@
<?php
namespace Mockery;
class VerificationExpectation extends Expectation
{
public function clearCountValidators()
{
$this->_countValidators = array();
}
public function __clone()
{
parent::__clone();
$this->_actualCount = 0;
}
}
Oops, something went wrong.

0 comments on commit aab7d1c

Please sign in to comment.