Permalink
Browse files

Merge pull request #283 from davedevelopment/spies

Basic/naive spy implementation
  • Loading branch information...
2 parents 627a6c8 + 31866ac commit aab7d1ccfa1caf4c22bd8d77947b28b48fc9c954 @davedevelopment davedevelopment committed Oct 7, 2014
View
@@ -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,
View
@@ -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.