Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Feature/mock features #788

Merged
merged 3 commits into from

3 participants

@blainesch
Collaborator

Possible fix for #784

@jails
Collaborator

I'm not sure you can really test the ordering of calls with this implementation.

What I mean here is:

assertCalled($mock, 'method1', 'eq', 1);
assertCalled($mock, 'method2', 'eq', 1);

//is different from:

assertCalled($mock, 'method2', 'eq', 1);
assertCalled($mock, 'method1', 'eq', 1);

Imo you need to keep a trace of your "run through" to not restart each time on your $mock::results (this of course imply an additionnal method, i.e a kind of $mock->flush() which allow restarting on $mock::results)

So regarding the syntax something like this looks like more "fluent" for managing the ordering of calls.

assertTrue($mock->call('method1')->with('a', 'b')->eq(1)
                ->call('method2')->with('c', 'd')->eq(1)->success());
@jails
Collaborator

Just wonder if we also need to differentiate calls from static calls.

@blainesch
Collaborator

I'm nervous about adding a lot of methods to Mock since every method I manually put it will not only be in string form but also can overwrite a method on the mocked class.

The $results currently store the arguments and return values so it's possible to change/add assertion methods to compensate for with, however testing the order of calls isn't something that's been implemented so would require a refactor.

@jails I'm not sure what you mean by your second comment "differentiate calls from static calls". Can you point me to the code you're looking at?

@jails
Collaborator

Imo you don't need to add all the methods in Mocker i.e.

assertTrue(Mocker::chain($mock)->called('method1')
                ->with('a', 'b')
                ->eq(1)
                ->called('method2')
                ->with('c', 'd')
                ->eq(1)
                ->success());

Mocker::chain($mock) returns what you called $results. So you only need here is to change $results to be an object instance which contain all the logic about the "chaining".

Mocker::chain($mock) will return $mock->results if $mock is an instance or $mock::results if $mock is a string. The second advantage here is you can change $results to $resultsMinimiumChanceToConflict if you consider $results may conflict.

For the calls/static calls, I wonder if we need to differentiate '::' from '->' call or maybe I missed something in your implementation ?

@blainesch
Collaborator

So the only functionality that isn't inside Mocker is logging the results in the order they were called. After that all the other functionality could be implemented in addition to the current pull request, unless you think that Mocker::chain should be implemented instead of assertCalled and assertNotCalled?

Honestly, all the chaining doesn't look very elegant to me. Maybe we can extend assertCalled to also check arguments similar to your with, update Mocker to add order to the method calls and then possible create another assertion for testing "before" assertCalledBefore($stub, 'method1', 'method2')?

@jails
Collaborator

It was just my thought on this subject, but I don't know if it's the best solution. I was not necessarily looking for something elegant first but a possible way to describe a flow of calls. May I miss something but I don't really see how you can describe a ->called('method1')->called('method2')->called('method1') with assertCalledBefore.

Regarding the elegance there will always be a way to hide the Mocker::chain($mock) with an assertCalled or something like that. But maybe @davidpersson @nateabele or @gwoo see what is the best way to go here.

Blaine Schme... added some commits
Blaine Schmeisser Add logging ability to Mocker for later assertions. c87910b
Blaine Schmeisser Update pass by reference logic.
Most methods cannot be passed by reference, however __get should mirror it's parent.
77a0f46
Blaine Schmeisser Add MockerChain to make assertions easier and extendible. 8c138f6
@blainesch
Collaborator

Alright @jails I'm convinced. I'm also not sure I like the class name MockerChain.

@nateabele nateabele commented on the diff
analysis/Debugger.php
@@ -16,7 +16,7 @@
* The `Debugger` class provides basic facilities for generating and rendering meta-data about the
* state of an application in its current context.
*/
-class Debugger extends \lithium\core\Object {
+class Debugger extends \lithium\core\StaticObject {
@nateabele Owner

A quick scan of Debugger seems to show it doesn't really need to extend anything. Thoughts?

@blainesch Collaborator

I assumed it was an unwritten lithium convention to always extend Object or StaticObject? It'll probably make it faster not to extend anything, but without benchmarks I'd probably say very minimal performance improvement. I like that most objects extend core functionality.

The only side effect I can think of is if it doesn't extend one of them it cannot be used by Mocker since it will no longer have access to applyFilter.

@nateabele Owner

I assumed it was an unwritten lithium convention to always extend Object or StaticObject?

Really only when necessary.

The only side effect I can think of is if it doesn't extend one of them it cannot be used by Mocker since it will no longer have access to applyFilter.

I'm not sure I follow, since Debugger doesn't have any filterable methods.

@blainesch Collaborator

Right, no filterable methods exist. However if I try to autoload lithium\analysis\debugger\Mock now all (well most) methods are filterable. However if the class doesn't extend Object or StaticObject the Mocker autoloader won't even try to create it.

@nateabele Owner

Okay, gotcha. Never mind then, carry on. :-)

@jails Collaborator
jails added a note

Pure hypothesis, since we need to override all methods in the Mock to make them filterable, can't we add to Mock it's own Object + StaticObject logic ?
i.e.

  • Mock will simply be a kind of "proxy" to the mocked class/instance.
  • an simple if (isset($this)) allow to differenciate Mock::applyFilter from $mock->applyFilter call.
  • According the ReflectionClass we know if a method is static or not so we can build the correct filter.

This way the Mocker can Mock any kind of class with a small "footprints" (i.e adding applyFilter, _filter and $_methodFilters to the attributes/methods). And this way Debugger doesn't need to extends anything to be mocked.

Does that make sense ?

@blainesch Collaborator

That's an interesting idea. It sounds like it would work, I'll try and tackle that for my next pr. It would be nice to be able to 'mock' any class.

@nateabele If there are no other side effects of not extending Object or StaticObject, to increase performance would a pr be accepted that looked through and manually pulled out unnecessary extensions? Or do you think it's too few to be worth the time?

@jails Collaborator
jails added a note

As an afterthought, looks like mixing Object and StaticObject will break the strict standards (i.e. Non-static method should not be called statically). Moreover Models override the default StaticObject filter system so here the Mock must extends Model to make it work.

However I'm pretty sure we can find an alternative by dynamically extending the Mock from the mocked class if the mocked extends StaticObject or Object. And for other kind of class, the Mock will extends StaticObject or Object depending on "something" (something can be replaced here by a big question mark). Or maybe using two keywords Mock|StaticMock which indicate the type of the class ? Need to think about it.

@nateabele Owner

@BlaineSch The thing with Debugger is that there's really no reason anyone would ever extend it. The thing with going through every class in the codebase is that there are very few other classes for which that is definitively, absolutely true. So yeah, not worth it, IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@blainesch
Collaborator

Any other concerns with this pull request, or can it be merged in?

@nateabele
Owner

Yeah, sorry, no other concerns.

@nateabele nateabele merged commit ddf711f into UnionOfRAD:dev

1 check passed

Details default The Travis build passed
@blainesch blainesch deleted the unknown repository branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 17, 2013
  1. Add logging ability to Mocker for later assertions.

    Blaine Schmeisser authored
  2. Update pass by reference logic.

    Blaine Schmeisser authored
    Most methods cannot be passed by reference, however __get should mirror it's parent.
  3. Add MockerChain to make assertions easier and extendible.

    Blaine Schmeisser authored
This page is out of date. Refresh to see the latest.
View
2  analysis/Debugger.php
@@ -16,7 +16,7 @@
* The `Debugger` class provides basic facilities for generating and rendering meta-data about the
* state of an application in its current context.
*/
-class Debugger extends \lithium\core\Object {
+class Debugger extends \lithium\core\StaticObject {
@nateabele Owner

A quick scan of Debugger seems to show it doesn't really need to extend anything. Thoughts?

@blainesch Collaborator

I assumed it was an unwritten lithium convention to always extend Object or StaticObject? It'll probably make it faster not to extend anything, but without benchmarks I'd probably say very minimal performance improvement. I like that most objects extend core functionality.

The only side effect I can think of is if it doesn't extend one of them it cannot be used by Mocker since it will no longer have access to applyFilter.

@nateabele Owner

I assumed it was an unwritten lithium convention to always extend Object or StaticObject?

Really only when necessary.

The only side effect I can think of is if it doesn't extend one of them it cannot be used by Mocker since it will no longer have access to applyFilter.

I'm not sure I follow, since Debugger doesn't have any filterable methods.

@blainesch Collaborator

Right, no filterable methods exist. However if I try to autoload lithium\analysis\debugger\Mock now all (well most) methods are filterable. However if the class doesn't extend Object or StaticObject the Mocker autoloader won't even try to create it.

@nateabele Owner

Okay, gotcha. Never mind then, carry on. :-)

@jails Collaborator
jails added a note

Pure hypothesis, since we need to override all methods in the Mock to make them filterable, can't we add to Mock it's own Object + StaticObject logic ?
i.e.

  • Mock will simply be a kind of "proxy" to the mocked class/instance.
  • an simple if (isset($this)) allow to differenciate Mock::applyFilter from $mock->applyFilter call.
  • According the ReflectionClass we know if a method is static or not so we can build the correct filter.

This way the Mocker can Mock any kind of class with a small "footprints" (i.e adding applyFilter, _filter and $_methodFilters to the attributes/methods). And this way Debugger doesn't need to extends anything to be mocked.

Does that make sense ?

@blainesch Collaborator

That's an interesting idea. It sounds like it would work, I'll try and tackle that for my next pr. It would be nice to be able to 'mock' any class.

@nateabele If there are no other side effects of not extending Object or StaticObject, to increase performance would a pr be accepted that looked through and manually pulled out unnecessary extensions? Or do you think it's too few to be worth the time?

@jails Collaborator
jails added a note

As an afterthought, looks like mixing Object and StaticObject will break the strict standards (i.e. Non-static method should not be called statically). Moreover Models override the default StaticObject filter system so here the Mock must extends Model to make it work.

However I'm pretty sure we can find an alternative by dynamically extending the Mock from the mocked class if the mocked extends StaticObject or Object. And for other kind of class, the Mock will extends StaticObject or Object depending on "something" (something can be replaced here by a big question mark). Or maybe using two keywords Mock|StaticMock which indicate the type of the class ? Need to think about it.

@nateabele Owner

@BlaineSch The thing with Debugger is that there's really no reason anyone would ever extend it. The thing with going through every class in the codebase is that there are very few other classes for which that is definitively, absolutely true. So yeah, not worth it, IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
/**
* Used for temporary closure caching.
View
107 test/Mocker.php
@@ -22,7 +22,7 @@
* {{{
* use lithium\core\Environment;
* use lithium\test\Mocker;
- * if(!Environment::is('production')) {
+ * if (!Environment::is('production')) {
* Mocker::register();
* }
* }}}
@@ -93,8 +93,7 @@ class Mocker {
' $method = array($this->parent, "{:method}");',
' return call_user_func_array($method, $args);',
' }',
- ' array_pop($args);',
- ' return call_user_func_array("parent::{:method}", $args);',
+ ' return call_user_func_array("parent::{:method}", compact({:stringArgs}));',
'}',
),
'staticMethod' => array(
@@ -106,8 +105,7 @@ class Mocker {
' $method = \'{:namespace}\Mock::{:method}\';',
' return call_user_func_array($method, $args);',
' }',
- ' array_pop($args);',
- ' return call_user_func_array("parent::{:method}", $args);',
+ ' return call_user_func_array("parent::{:method}", compact({:stringArgs}));',
'}',
),
'endClass' => array(
@@ -123,6 +121,9 @@ class Mocker {
* interface for consumers, instantiation or static method calls, and can
* have most of its methods filtered.
*
+ * The `$results` variable holds all method calls allowing you for you
+ * make your own custom assertions on them.
+ *
* @var array
*/
protected static $_mockIngredients = array(
@@ -130,16 +131,24 @@ class Mocker {
'namespace {:namespace};',
'class Mock extends \{:mocker} {',
' public $mocker;',
+ ' public {:static} $results = array();',
' protected $_safeVars = array(',
' "_classes",',
' "_methodFilters",',
' "mocker",',
- ' "_safeVars"',
+ ' "_safeVars",',
+ ' "results",',
' );',
),
'get' => array(
- 'public function __get($key) {',
- ' return $this->mocker->$key;',
+ 'public function {:reference}__get($name) {',
+ ' $data ={:reference} $this->mocker->$name;',
+ ' return $data;',
+ '}',
+ ),
+ 'set' => array(
+ 'public function __set($name, $value = null) {',
+ ' return $this->mocker->$name = $value;',
'}',
),
'constructor' => array(
@@ -160,22 +169,40 @@ class Mocker {
),
'staticMethod' => array(
'{:modifiers} function {:method}({:args}) {',
- ' $args = func_get_args();',
- ' array_push($args, "1f3870be274f6c49b3e31a0c6728957f");',
+ ' $args = compact({:stringArgs});',
+ ' $args["hash"] = "1f3870be274f6c49b3e31a0c6728957f";',
' $method = \'{:namespace}\MockDelegate::{:method}\';',
- ' return self::_filter("{:method}", $args, function($self, $args) use(&$method) {',
+ ' $result = self::_filter("{:method}", $args, function($self, $args) use(&$method) {',
' return call_user_func_array($method, $args);',
' });',
+ ' if (!isset(self::$results["{:method}"])) {',
+ ' self::$results["{:method}"] = array();',
+ ' }',
+ ' self::$results["{:method}"][] = array(',
+ ' "args" => func_get_args(),',
+ ' "result" => $result,',
+ ' "time" => microtime(true),',
+ ' );',
+ ' return $result;',
'}',
),
'method' => array(
'{:modifiers} function {:method}({:args}) {',
- ' $args = func_get_args();',
- ' array_push($args, spl_object_hash($this->mocker));',
+ ' $args = compact({:stringArgs});',
+ ' $args["hash"] = spl_object_hash($this->mocker);',
' $method = array($this->mocker, "{:method}");',
- ' return $this->_filter(__METHOD__, $args, function($self, $args) use(&$method) {',
+ ' $result = $this->_filter(__METHOD__, $args, function($self, $args) use(&$method) {',
' return call_user_func_array($method, $args);',
' });',
+ ' if (!isset($this->results["{:method}"])) {',
+ ' $this->results["{:method}"] = array();',
+ ' }',
+ ' $this->results["{:method}"][] = array(',
+ ' "args" => func_get_args(),',
+ ' "result" => $result,',
+ ' "time" => microtime(true),',
+ ' );',
+ ' return $result;',
'}',
),
'endClass' => array(
@@ -192,8 +219,9 @@ class Mocker {
'__destruct', '__call', '__callStatic', '_parents',
'__get', '__set', '__isset', '__unset', '__sleep',
'__wakeup', '__toString', '__clone', '__invoke',
- '_stop', '_init', 'applyFilter', 'invokeMethod',
- '__set_state', '_instance', '_filter',
+ '_stop', '_init', 'invokeMethod', '__set_state',
+ '_instance', '_filter', '_object', '_initialize',
+ 'applyFilter',
);
/**
@@ -217,35 +245,49 @@ public static function create($mockee) {
}
$mocker = self::_mocker($mockee);
+ $isStatic = is_subclass_of($mocker, 'lithium\core\StaticObject');
$tokens = array(
'namespace' => self::_namespace($mockee),
'mocker' => $mocker,
'mockee' => 'MockDelegate',
+ 'static' => $isStatic ? 'static' : '',
);
$mockDelegate = self::_dynamicCode('mockDelegate', 'startClass', $tokens);
$mock = self::_dynamicCode('mock', 'startClass', $tokens);
$reflectedClass = new ReflectionClass($mocker);
$reflecedMethods = $reflectedClass->getMethods();
- foreach ($reflecedMethods as $method) {
+ $getByReference = false;
+ foreach ($reflecedMethods as $methodId => $method) {
if (!in_array($method->name, self::$_blackList)) {
$key = $method->isStatic() ? 'staticMethod' : 'method';
$key = $method->name === '__construct' ? 'constructor' : $key;
+ $docs = ReflectionMethod::export($mocker, $method->name, true);
+ if (preg_match('/&' . $method->name . '/', $docs) === 1) {
+ continue;
+ }
$tokens = array(
'namespace' => self::_namespace($mockee),
'method' => $method->name,
'modifiers' => self::_methodModifiers($method),
'args' => self::_methodParams($method),
+ 'stringArgs' => self::_stringMethodParams($method),
'mocker' => $mocker,
);
$mockDelegate .= self::_dynamicCode('mockDelegate', $key, $tokens);
$mock .= self::_dynamicCode('mock', $key, $tokens);
+ } elseif ($method->name === '__get') {
+ $docs = ReflectionMethod::export($mocker, '__get', true);
+ $getByReference = preg_match('/&__get/', $docs) === 1;
}
}
$mockDelegate .= self::_dynamicCode('mockDelegate', 'endClass');
- $mock .= self::_dynamicCode('mock', 'get');
+ $mock .= self::_dynamicCode('mock', 'get', array(
+ 'reference' => $getByReference ? '&' : '',
+ ));
+ $mock .= self::_dynamicCode('mock', 'set');
$mock .= self::_dynamicCode('mock', 'destructor');
$mock .= self::_dynamicCode('mock', 'endClass');
@@ -287,6 +329,19 @@ protected static function _methodParams(ReflectionMethod $method) {
}
/**
+ * Will return the params in a way that can be placed into `compact()`
+ *
+ * @param ReflectionMethod $method
+ * @return string
+ */
+ protected static function _stringMethodParams(ReflectionMethod $method) {
+ $pattern = '/Parameter [^$]+\$([^ ]+)/';
+ preg_match_all($pattern, $method, $matches);
+ $params = implode("', '", $matches[1]);
+ return strlen($params) > 0 ? "'{$params}'" : 'array()';
+ }
+
+ /**
* Will generate the code you are wanting.
*
* This pulls from $_mockDelegateIngredients and $_mockIngredients.
@@ -348,6 +403,22 @@ protected static function _validateMockee($mockee) {
return true;
}
+ /**
+ * Generate a chain class with the current rules of the mock.
+ *
+ * @param object $mock Mock to chain
+ * @return object MockerChain instance
+ */
+ public static function chain($mock) {
+ $results = array();
+ if (is_object($mock) && isset($mock->results)) {
+ $results = $mock->results;
+ } elseif (is_string($mock) && class_exists($mock) && isset($mock::$results)) {
+ $results = $mock::$results;
+ }
+ return new MockerChain($results);
+ }
+
}
?>
View
196 test/MockerChain.php
@@ -0,0 +1,196 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\test;
+
+/**
+ * Mocker chain is used to aid in assertion of method calls.
+ *
+ * Asserting if `method1` was not called
+ * {{{
+ * $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ * $this->assertFalse(Mocker::chain($mock)->called('method1')->success());
+ * }}}
+ *
+ * Asserting if `method1` was called 2 times
+ * {{{
+ * $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ * $this->assertTrue(Mocker::chain($mock)->called('method1')->eq(2)->success());
+ * }}}
+ *
+ * Asserting if `method2` was called after `method1`
+ * {{{
+ * $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ * $this->assertTrue(Mocker::chain($mock)->called('method1')->called('method2')->success());
+ * }}}
+ *
+ * Asserting if `method2` was called after `method1`, and `method2` had specific arguments.
+ * {{{
+ * $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ * $this->assertTrue(Mocker::chain($mock)
+ * ->called('method1')
+ * ->called('method2')->with('foo', 'bar')
+ * ->success());
+ * }}}
+ */
+class MockerChain extends \lithium\core\Object {
+
+ /**
+ * Data to be used in the class.
+ *
+ * `results` Cached mock results
+ * `method` Method we are asserting
+ * `args` Args we are asserting
+ * `success` Success flag
+ * `callTime` Last method call
+ *
+ * @var array
+ */
+ protected $_data = array(
+ 'results' => null,
+ 'method' => false,
+ 'args' => false,
+ 'success' => true,
+ 'callTime' => 0,
+ );
+
+ /**
+ * Saves the results from the mock.
+ *
+ * @param array $results Results from the mock
+ */
+ public function __construct($results) {
+ $this->_data['results'] = $results;
+ }
+
+ /**
+ * Validates that a given methodis called a set number of times.
+ *
+ * @param string $comparison Comparison type 'gt', 'gte', 'lt', 'lte', or 'eq'.
+ * @param array $args The first argument is the expected result.
+ * @return object
+ */
+ public function __call($comparison, $args) {
+ $methodExists = in_array($comparison, array('gt', 'gte', 'lt', 'lte', 'eq'), true);
+ if (!$this->_data['success'] || !$methodExists) {
+ return $this;
+ }
+ if (count($args) === 0 || !is_int($args[0])) {
+ $this->_data['success'] = false;
+ return $this;
+ }
+ $result = 0;
+ $expected = $args[0];
+ $method = $this->_data['method'];
+ $args = $this->_data['args'];
+ foreach ($this->_data['results'][$method] as $call) {
+ $correctTime = $this->_data['callTime'] <= $call['time'];
+ $correctArgs = !is_array($args) || $args === $call['args'];
+ if ($correctTime && $correctArgs) {
+ $this->_data['callTime'] = $call['time'];
+ $result++;
+ }
+ }
+ switch ($comparison) {
+ case 'gt':
+ $this->_data['success'] = $result > $expected;
+ break;
+ case 'gte':
+ $this->_data['success'] = $result >= $expected;
+ break;
+ case 'lt':
+ $this->_data['success'] = $result < $expected;
+ break;
+ case 'lte':
+ $this->_data['success'] = $result <= $expected;
+ break;
+ case 'eq':
+ $this->_data['success'] = $result === $expected;
+ break;
+ }
+ return $this;
+ }
+
+ /**
+ * Valides the method was called after the last call.
+ *
+ * @param string $method Method to assert
+ * @return object
+ */
+ function called($method) {
+ if (!$this->_data['success']) {
+ return $this;
+ }
+
+ $this->_data['method'] = $method;
+ $this->_data['args'] = false;
+ if (!isset($this->_data['results'][$method])) {
+ $this->_data['success'] = false;
+ return $this;
+ }
+
+ foreach ($this->_data['results'][$method] as $call) {
+ if ($this->_data['callTime'] < $call['time']) {
+ $this->_data['callTime'] = $call['time'];
+ return $this;
+ }
+ }
+
+ $this->_data['success'] = false;
+ return $this;
+ }
+
+ /**
+ * Will further narrow down the original 'called' method.
+ *
+ * Valides the cached method name was called with these args
+ *
+ * @param mixed $arg,... Optional arguments to test against
+ * @return object
+ */
+ public function with() {
+ if (!$this->_data['success']) {
+ return $this;
+ }
+
+ $method = $this->_data['method'];
+ $this->_data['args'] = $args = func_get_args();
+
+ foreach ($this->_data['results'][$method] as $call) {
+ $correctTime = $this->_data['callTime'] <= $call['time'];
+ $correctArgs = $args === $call['args'];
+ if ($correctTime && $correctArgs) {
+ $this->_data['callTime'] = $call['time'];
+ return $this;
+ }
+ }
+
+ $this->_data['success'] = false;
+ return $this;
+ }
+
+ /**
+ * Gives back the success flag
+ *
+ * @return bool
+ */
+ public function success() {
+ $success = $this->_data['success'];
+ $this->_data = array(
+ 'results' => $this->_data['results'],
+ 'method' => false,
+ 'args' => false,
+ 'success' => true,
+ 'callTime' => 0,
+ );
+ return $success;
+ }
+
+}
+
+?>
View
127 tests/cases/test/MockerChainTest.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\tests\cases\test;
+
+use lithium\test\Mocker;
+
+class MockerChainTest extends \lithium\test\Unit {
+
+ public function setUp() {
+ Mocker::register();
+ }
+
+ public function testStartSuccessful() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $chain = Mocker::chain($mock);
+
+ $this->assertTrue($chain->success());
+ }
+
+ public function testBasicNotCalled() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $chain = Mocker::chain($mock);
+
+ $this->assertFalse($chain->called('method1')->success());
+ }
+
+ public function testBasicCalled() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $mock->method1();
+ $chain = Mocker::chain($mock);
+
+ $this->assertTrue($chain->called('method1')->success());
+ }
+
+ public function testCalledWith() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $mock->method1('foo');
+ $chain = Mocker::chain($mock);
+
+ $this->assertTrue($chain->called('method1')->success());
+ $this->assertFalse($chain->called('method1')->with('bar')->success());
+ $this->assertTrue($chain->called('method1')->with('foo')->success());
+ $this->assertFalse($chain->called('method1')->with('foo', 'bar')->success());
+ }
+
+ public function testMethodCalledBefore() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $mock->method1();
+ $mock->method2();
+ $mock->method1();
+ $chain = Mocker::chain($mock);
+
+ $this->assertTrue($chain->called('method1')
+ ->called('method2')
+ ->called('method1')
+ ->success());
+ $this->assertFalse($chain->called('method2')
+ ->called('method1')
+ ->called('method1')
+ ->success());
+ }
+
+ public function testMethodWithParamsCalledBefore() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $mock->method1('foo');
+ $mock->method2('bar');
+ $mock->method1('baz');
+ $chain = Mocker::chain($mock);
+
+ $this->assertTrue($chain->called('method1')
+ ->called('method2')->with('bar')
+ ->called('method1')
+ ->success());
+ $this->assertFalse($chain->called('method1')->with('bar')
+ ->called('method2')->with('bar')
+ ->called('method1')
+ ->success());
+ $this->assertFalse($chain->called('method1')
+ ->called('method2')->with('bar')
+ ->called('method1')->with('bar')
+ ->success());
+ }
+
+ public function testMethodCalledSpecificTimes() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $mock->method1();
+ $mock->method2();
+ $mock->method1();
+ $chain = Mocker::chain($mock);
+
+ $this->assertFalse($chain->called('method2')->eq(2)->success());
+ $this->assertTrue($chain->called('method1')->eq(2)->success());
+ $this->assertTrue($chain->called('method1')->gt(0)->success());
+ $this->assertTrue($chain->called('method1')->gte(1)->success());
+ $this->assertTrue($chain->called('method1')->lt(3)->success());
+ $this->assertTrue($chain->called('method1')->lte(2)->success());
+ $this->assertFalse($chain->called('method1')->lte(1)->success());
+ }
+
+ public function testMultipleCallsWithArgsAndSpecificCalled() {
+ $mock = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $mock->method1('foo', 'bar');
+ $mock->method1('foo', 'bar');
+ $mock->method1('foo', 'bar');
+ $mock->method2('baz');
+ $mock->method2('baz');
+ $mock->method1();
+ $chain = Mocker::chain($mock);
+
+ $this->assertTrue($chain->called('method1')->with('foo', 'bar')->eq(3)->success());
+ $this->assertTrue($chain->called('method2')->with('baz')->eq(2)->success());
+ $this->assertTrue($chain->called('method1')->with()->eq(1)->success());
+
+ $this->assertTrue($chain->called('method1')->with('foo', 'bar')->eq(3)
+ ->called('method2')->with('baz')->eq(2)
+ ->called('method1')->with()->eq(1)->success());
+ }
+
+}
+
+?>
View
71 tests/cases/test/MockerTest.php
@@ -12,7 +12,7 @@
/**
* WARNING:
- * No unit test should mock the same test as another
+ * No unit test should mock the same test as another to avoid conflicting filters.
*/
class MockerTest extends \lithium\test\Unit {
@@ -56,13 +56,13 @@ public function testCannotCreateNonStandardMockClass() {
public function testFilteringNonStaticClass() {
$dispatcher = new \lithium\console\dispatcher\Mock();
- $originalResult = $dispatcher->config();
+ $originalResult = $dispatcher->config(array());
$dispatcher->applyFilter('config', function($self, $params, $chain) {
return array();
});
- $filteredResult = $dispatcher->config();
+ $filteredResult = $dispatcher->config(array());
$this->assertEqual(0, count($filteredResult));
$this->assertNotEqual($filteredResult, $originalResult);
@@ -136,7 +136,70 @@ public function testFilteringAFilteredMethod() {
$adapt::applyFilter('_initAdapter', function($self, $params, $chain) {
return false;
});
- $this->assertFalse($adapt::_initAdapter('foo', array()));
+ $this->assertIdentical(false, $adapt::_initAdapter('foo', array()));
+ }
+
+ public function testStaticResults() {
+ $docblock = 'lithium\analysis\docblock\Mock';
+ $docblock::applyFilter(array('comment', 'tags'), function($self, $params, $chain) {
+ return false;
+ });
+ $docblock::comment('foo', 'foobar');
+ $docblock::comment('bar');
+ $docblock::tags('baz');
+
+ $this->assertIdentical(2, count($docblock::$results['comment']));
+ $this->assertIdentical(array('foo', 'foobar'), $docblock::$results['comment'][0]['args']);
+ $this->assertIdentical(false, $docblock::$results['comment'][0]['result']);
+ $this->assertIdentical(array('bar'), $docblock::$results['comment'][1]['args']);
+ $this->assertIdentical(false, $docblock::$results['comment'][1]['result']);
+
+ $this->assertIdentical(1, count($docblock::$results['tags']));
+ $this->assertIdentical(array('baz'), $docblock::$results['tags'][0]['args']);
+ $this->assertIdentical(false, $docblock::$results['tags'][0]['result']);
+ }
+
+ public function testInstanceResults() {
+ $debugger = new \lithium\data\schema\Mock;
+ $debugger->applyFilter(array('names', 'meta'), function($self, $params, $chain) {
+ return false;
+ });
+ $debugger->names('foo', 'foobar');
+ $debugger->names('bar');
+ $debugger->meta('baz');
+
+ $this->assertIdentical(2, count($debugger->results['names']));
+ $this->assertIdentical(array('foo', 'foobar'), $debugger->results['names'][0]['args']);
+ $this->assertIdentical(false, $debugger->results['names'][0]['result']);
+ $this->assertIdentical(array('bar'), $debugger->results['names'][1]['args']);
+ $this->assertIdentical(false, $debugger->results['names'][1]['result']);
+
+ $this->assertIdentical(1, count($debugger->results['meta']));
+ $this->assertIdentical(array('baz'), $debugger->results['meta'][0]['args']);
+ $this->assertIdentical(false, $debugger->results['meta'][0]['result']);
+ }
+
+ public function testSkipByReference() {
+ $stdObj = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $stdObj->foo = 'foo';
+ $originalData = $stdObj->data();
+ $stdObj->applyFilter('data', function($self, $params, $chain) {
+ return array();
+ });
+ $nonfilteredData = $stdObj->data();
+ $this->assertIdentical($originalData, $nonfilteredData);
+ }
+
+ public function testGetByReference() {
+ $stdObj = new \lithium\tests\mocks\test\mockStdClass\Mock();
+ $stdObj->foo = 'foo';
+ $foo =& $stdObj->foo;
+ $foo = 'bar';
+ $this->assertIdentical('bar', $stdObj->foo);
+ }
+
+ public function testChainReturnsMockerChain() {
+ $this->assertTrue(Mocker::chain(new \stdClass) instanceof \lithium\test\MockerChain);
}
}
View
41 tests/mocks/test/MockStdClass.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace lithium\tests\mocks\test;
+
+class MockStdClass extends \lithium\core\Object {
+
+ protected $_data = array();
+
+ public function __set($key, $value) {
+ return $this->_data[$key] = $value;
+ }
+
+ public function &__get($key) {
+ if (isset($this->_data[$key])) {
+ $data =& $this->_data[$key];
+ return $data;
+ }
+ $data = null;
+ return $data;
+ }
+
+ public function &data() {
+ $data =& $this->_data;
+ return $data;
+ }
+
+ public function filterableData() {
+ return $this->_data;
+ }
+
+ public function method1() {
+ return true;
+ }
+
+ public function method2() {
+ return false;
+ }
+
+}
+
+?>
Something went wrong with that request. Please try again.