Skip to content

Commit 98de15a

Browse files
committed
implemented mocking of functions
1 parent 024ceb4 commit 98de15a

15 files changed

Lines changed: 427 additions & 27 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
#### 0.5.0
44

5+
* Mocking functions implemented with test::func method *2014-10-10*
6+
* Fixed mocking functions with arguments passed by reference #34 by @tampe125 *2014-10-09*
7+
* Fixed mocking functions with arguments passed by reference #34 by @tampe125 *2014-10-08*
58
* Fixed passing arguments by reference in InstanceProxy. Thanks to @tampe125. Issue #39 *2014-10-08*
69
* Debug mode can be disabled in options with debug => false *2014-10-08*
710
* Updated to Go\Aop 0.5.0

RoboFile.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ class Robofile extends \Robo\Tasks
66
protected $docs = [
77
'docs/Test.md' => 'AspectMock\Test',
88
'docs/ClassProxy.md' => 'AspectMock\Proxy\ClassProxy',
9-
'docs/InstanceProxy.md' => 'AspectMock\Proxy\InstanceProxy'
9+
'docs/InstanceProxy.md' => 'AspectMock\Proxy\InstanceProxy',
10+
'docs/FuncProxy.md' => 'AspectMock\Proxy\FuncProxy'
1011
];
1112

1213
protected function version()
@@ -52,6 +53,7 @@ function (\ReflectionMethod $m, $doc) {
5253
$doc = str_replace(array(' @', "\n@"), array(" * ", "\n * "), $doc);
5354
return $doc;
5455
})
56+
->processProperty(false)
5557
->run();
5658
}
5759
}

docs/ClassProxy.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ $user->getCallsForMethod('someMethod') // [ ['arg1', 'arg2'] ]
4040
?>
4141
```
4242

43-
#### *public* classNameName of a class.
4443

45-
@var
4644

4745

4846

docs/FuncProxy.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
## AspectMock\Proxy\FuncProxy
3+
4+
* *Extends* `AspectMock\Proxy\Verifier`
5+
6+
FuncProxy is a wrapper around mocked function, used to verify function calls.
7+
Has the same verification methods as `InstanceProxy` and `ClassProxy` do.
8+
9+
Usage:
10+
11+
```php
12+
<?php
13+
namespace Acme\User;
14+
$func = test::func('Acme\User', 'strlen', 10);
15+
strlen('hello');
16+
strlen('world');
17+
$func->verifyInvoked(); // true
18+
$func->verifyInvoked(['hello']); // true
19+
$func->verifyInvokedMultipleTimes(2);
20+
$func->verifyNeverInvoked(['bye']);
21+
22+
```
23+
24+
25+
26+
27+
28+
29+
30+
31+
32+
33+
#### *public* verifyInvoked($params = null)
34+
* `param null` $params
35+
36+
#### *public* verifyInvokedOnce($params = null)
37+
* `param null` $params
38+
39+
#### *public* verifyNeverInvoked($params = null)
40+
* `param null` $params
41+
42+
#### *public* verifyInvokedMultipleTimes($times, $params = null)
43+
* `param` $times
44+
* `param null` $params
45+
46+
47+
#### *public* getCallsForMethod($func)
48+

docs/InstanceProxy.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ $user->getCallsForMethod('someMethod') // [ ['arg1', 'arg2'] ]
5353
```
5454

5555

56-
#### *public* classNameName of a class.
5756

58-
@var
5957

6058

6159

docs/Test.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Example:
3131
$user = test::double(new User, ['getName' => 'davert']);
3232
$user->getName() // => davert
3333
$user->verifyInvoked('getName'); // => success
34+
$user->getObject() // => returns instance of User, i.e. real, not proxified object
3435

3536
# with closure
3637
$user = test::double(new User, ['getName' => function() { return $this->login; }]);
@@ -132,10 +133,7 @@ If class is already defined, `test::spec` will act as `test::double`.
132133
* return Verifier
133134

134135

135-
#### *public static* methods($classOrObject, array $only = Array
136-
(
137-
)
138-
)
136+
#### *public static* methods($classOrObject, array $only = Array ( ) )
139137
Replaces all methods in a class with a dummies, except specified.
140138

141139
``` php
@@ -179,6 +177,42 @@ When declared in `test::double` not exists, AspectMock will try to match it by p
179177
To ignore namespace guessing, use `\` in the beginning of class name: `\User`;
180178

181179

180+
#### *public static* func($namespace, $function, $body)
181+
Replaces function in provided namespace with user-defined function or value that function returns;
182+
Function is restored to original on cleanup.
183+
184+
```php
185+
<?php
186+
namespace demo;
187+
test::func('demo', 'date', 2004);
188+
date('Y'); // 2004
189+
190+
test::func('demo', 'date', function($format) {
191+
if ($format == 'Y') {
192+
return 2004;
193+
} else {
194+
return \date($param);
195+
}
196+
}
197+
198+
```
199+
200+
Mocked functions can be verified for calls.
201+
202+
```php
203+
<?php
204+
namespace demo;
205+
$func = test::func('demo', 'date', 2004);
206+
date('Y'); // 2004
207+
$func->verifyInvoked();
208+
$func->verifyInvokedOnce(['Y']);
209+
```
210+
211+
* `param` $namespace
212+
* `param` $function
213+
* `param` $body
214+
* return Proxy\FuncProxy
215+
182216
#### *public static* clean($classOrInstance = null)
183217
Clears test doubles registry.
184218
Should be called between tests.

src/AspectMock/Core/Mocker.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22
namespace AspectMock\Core;
3+
use AspectMock\Intercept\FunctionInjector;
34
use Go\Aop\Aspect;
45
use AspectMock\Intercept\MethodInvocation;
56

@@ -35,6 +36,23 @@ public function fakeMethodsAndRegisterCalls($class, $declaredClass, $method, $pa
3536
return $result;
3637
}
3738

39+
public function fakeFunctionAndRegisterCalls($namespace, $function, $args)
40+
{
41+
$result = __AM_CONTINUE__;
42+
$fullFuncName = "$namespace\\$function";
43+
Registry::registerFunctionCall($fullFuncName, $args);
44+
45+
if (isset($this->funcMap[$fullFuncName])) {
46+
$func = $this->funcMap[$fullFuncName];
47+
if (is_callable($func)) {
48+
$result = call_user_func_array($func, $args);
49+
} else {
50+
$result = $func;
51+
}
52+
}
53+
return $result;
54+
}
55+
3856
protected function invokeFakedMethods(MethodInvocation $invocation)
3957
{
4058
$method = $invocation->getMethod();
@@ -172,6 +190,16 @@ public function registerObject($object, $params = array())
172190
$this->methodMap = array_merge($this->methodMap, array_keys($params));
173191
}
174192

193+
public function registerFunc($namespace, $func, $body)
194+
{
195+
if (!function_exists("$namespace\\$func")) {
196+
$injector = new FunctionInjector($namespace, $func);
197+
$injector->save();
198+
$injector->inject();
199+
}
200+
$this->funcMap["$namespace\\$func"] = $body;
201+
}
202+
175203
public function clean($objectOrClass = null)
176204
{
177205
if (!$objectOrClass) {

src/AspectMock/Core/Registry.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Registry {
1313

1414
protected static $classCalls = [];
1515
protected static $instanceCalls = [];
16+
protected static $funcCalls = [];
1617
protected static $ns = '';
1718

1819
/**
@@ -30,9 +31,9 @@ static function registerObject($object, $params = array())
3031
self::$mocker->registerObject($object, $params);
3132
}
3233

33-
static function registerFunc($func, $resultOrClosure)
34+
static function registerFunc($namespace, $function, $resultOrClosure)
3435
{
35-
self::$mocker->registerFunc($func, $resultOrClosure);
36+
self::$mocker->registerFunc($namespace, $function, $resultOrClosure);
3637
}
3738

3839
static function getClassCallsFor($class)
@@ -50,6 +51,11 @@ static function getInstanceCallsFor($instance)
5051
: [];
5152
}
5253

54+
static function getFuncCallsFor($func)
55+
{
56+
return isset(self::$funcCalls[$func]) ? self::$funcCalls[$func] : [];
57+
}
58+
5359
static function clean($classOrInstance = null)
5460
{
5561
$classOrInstance = self::getRealClassOrObject($classOrInstance);
@@ -71,6 +77,7 @@ static function cleanInvocations()
7177
{
7278
self::$instanceCalls = [];
7379
self::$classCalls = [];
80+
self::$funcCalls = [];
7481
}
7582

7683
static function registerInstanceCall($instance, $method, $args = array())
@@ -94,6 +101,15 @@ static function registerClassCall($class, $method, $args = array())
94101

95102
}
96103

104+
static function registerFunctionCall($functionName, $args)
105+
{
106+
if (!isset(self::$funcCalls[$functionName])) self::$funcCalls[$functionName] = [];
107+
108+
isset(self::$funcCalls[$functionName])
109+
? self::$funcCalls[$functionName][] = $args
110+
: self::$funcCalls[$functionName] = array($args);
111+
}
112+
97113
public static function getRealClassOrObject($classOrObject)
98114
{
99115
if ($classOrObject instanceof ClassProxy) return $classOrObject->className;

src/AspectMock/Intercept/BeforeMockTransformer.php

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
use TokenReflection\ReflectionClass as ParsedClass;
1111
use TokenReflection\ReflectionFileNamespace as ParsedFileNamespace;
1212

13-
class BeforeMockTransformer extends WeavingTransformer {
14-
13+
class BeforeMockTransformer extends WeavingTransformer
14+
{
1515
protected $before = " if ((\$__am_res = __amock_before(\$this, __CLASS__, __FUNCTION__, array(%s), false)) !== __AM_CONTINUE__) return \$__am_res; ";
1616
protected $beforeStatic = " if ((\$__am_res = __amock_before(get_called_class(), __CLASS__, __FUNCTION__, array(%s), true)) !== __AM_CONTINUE__) return \$__am_res; ";
1717

@@ -66,33 +66,42 @@ public function transform(StreamMetaData $metadata)
6666

6767
$methods = $class->getMethods();
6868
foreach ($methods as $method) {
69-
/** @var $method ReflectionMethod` **/
70-
if ($method->getDeclaringClassName() != $class->getName()) continue;
69+
/** @var $method ReflectionMethod` * */
70+
if ($method->getDeclaringClassName() != $class->getName()) {
71+
continue;
72+
}
7173
// methods from traits have the same declaring class name, so check that the filenames match, too
72-
if ($method->getFileName() != $class->getFileName()) continue;
73-
if ($method->isAbstract()) continue;
74-
$beforeDefinition = $method->isStatic()
74+
if ($method->getFileName() != $class->getFileName()) {
75+
continue;
76+
}
77+
if ($method->isAbstract()) {
78+
continue;
79+
}
80+
$beforeDefinition = $method->isStatic()
7581
? $this->beforeStatic
7682
: $this->before;
7783
$reflectedParams = $method->getParameters();
7884

7985
$params = [];
8086

8187
foreach ($reflectedParams as $reflectedParam) {
82-
/** @var $reflectedParam ReflectionParameter **/
83-
$params[] = ($reflectedParam->isPassedByReference() ? '&$' : '$').$reflectedParam->getName();
88+
/** @var $reflectedParam ReflectionParameter * */
89+
$params[] = ($reflectedParam->isPassedByReference() ? '&$' : '$') . $reflectedParam->getName();
8490
}
8591
$params = implode(", ", $params);
8692
$beforeDefinition = sprintf($beforeDefinition, $params);
87-
for ($i = $method->getStartLine()-1; $i < $method->getEndLine()-1; $i++) {
88-
$pos = strpos($dataArray[$i],'{');
89-
if ($pos === false) continue;
90-
$dataArray[$i] = substr($dataArray[$i], 0, $pos+1).$beforeDefinition.substr($dataArray[$i], $pos+1);
93+
for ($i = $method->getStartLine() - 1; $i < $method->getEndLine() - 1; $i++) {
94+
$pos = strpos($dataArray[$i], '{');
95+
if ($pos === false) {
96+
continue;
97+
}
98+
$dataArray[$i] = substr($dataArray[$i], 0, $pos + 1) . $beforeDefinition . substr($dataArray[$i], $pos + 1);
9199
break;
92100
}
93101
}
94102
}
95103
}
96104
$metadata->source = implode("\n", $dataArray);
97105
}
106+
98107
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
namespace AspectMock\Intercept;
3+
4+
class FunctionInjector
5+
{
6+
protected $template = <<<EOF
7+
<?php
8+
namespace {{ns}};
9+
if (!function_exists('{{ns}}\{{func}}')) {
10+
function {{func}}() {
11+
if ((\$__am_res = __amock_before_func('{{ns}}','{{func}}', func_get_args())) !== __AM_CONTINUE__) {
12+
return \$__am_res;
13+
}
14+
return call_user_func_array('{{func}}', func_get_args());
15+
}
16+
}
17+
EOF;
18+
protected $namespace;
19+
protected $function;
20+
protected $fileName;
21+
22+
function __construct($namespace, $function)
23+
{
24+
$this->namespace = $namespace;
25+
$this->function = $function;
26+
$this->place('ns', $this->namespace);
27+
$this->place('func', $this->function);
28+
29+
}
30+
31+
public function save()
32+
{
33+
$this->fileName = tempnam(sys_get_temp_dir(), $this->function);
34+
file_put_contents($this->fileName, $this->template);
35+
}
36+
37+
public function inject()
38+
{
39+
require_once $this->fileName;
40+
}
41+
42+
public function getFileName()
43+
{
44+
return $this->fileName;
45+
}
46+
47+
public function getPHP()
48+
{
49+
return $this->template;
50+
}
51+
52+
protected function place($var, $value)
53+
{
54+
$this->template = str_replace("{{{$var}}}", $value, $this->template);
55+
}
56+
}

0 commit comments

Comments
 (0)