Skip to content

Commit

Permalink
Add MockerChain to make assertions easier and extendible.
Browse files Browse the repository at this point in the history
  • Loading branch information
Blaine Schmeisser committed Jan 17, 2013
1 parent 77a0f46 commit 8c138f6
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 0 deletions.
16 changes: 16 additions & 0 deletions test/Mocker.php
Expand Up @@ -403,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);
}

}

?>
196 changes: 196 additions & 0 deletions 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;
}

}

?>
127 changes: 127 additions & 0 deletions 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());
}

}

?>
4 changes: 4 additions & 0 deletions tests/cases/test/MockerTest.php
Expand Up @@ -198,6 +198,10 @@ public function testGetByReference() {
$this->assertIdentical('bar', $stdObj->foo);
}

public function testChainReturnsMockerChain() {
$this->assertTrue(Mocker::chain(new \stdClass) instanceof \lithium\test\MockerChain);
}

}

?>
8 changes: 8 additions & 0 deletions tests/mocks/test/MockStdClass.php
Expand Up @@ -28,6 +28,14 @@ public function filterableData() {
return $this->_data;
}

public function method1() {
return true;
}

public function method2() {
return false;
}

}

?>

0 comments on commit 8c138f6

Please sign in to comment.