Skip to content

Commit cb92342

Browse files
authored
Merge pull request #3 from xepozz/tracing
Tracing calls
2 parents aac7c60 + be8669e commit cb92342

File tree

6 files changed

+199
-14
lines changed

6 files changed

+199
-14
lines changed

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ functions as: `time()`, `str_contains()`, `rand`, etc.
77
[![Total Downloads](https://poser.pugx.org/xepozz/internal-mocker/downloads.svg)](https://packagist.org/packages/xepozz/internal-mocker)
88
[![phpunit](https://github.com/xepozz/internal-mocker/workflows/PHPUnit/badge.svg)](https://github.com/xepozz/internal-mocker/actions)
99

10+
# Table of contents
11+
12+
- [Installation](#installation)
13+
- [Usage](#usage)
14+
- [Register a hook](#register-a-hook)
15+
- [Register mocks](#register-mocks)
16+
- [Runtime mocks](#runtime-mocks)
17+
- [Pre-defined mock](#pre-defined-mock)
18+
- [Mix of two previous ways](#mix-of-two-previous-ways)
19+
- [State](#state)
20+
- [Tracking calls](#tracking-calls)
21+
- [Global namespaced functions](#global-namespaced-functions)
22+
- [Internal functions](#internal-functions)
23+
- [Workaround](#workaround)
24+
- [Internal function implementation](#internal-function-implementation)
25+
- [Restrictions](#restrictions)
26+
- [Data Providers](#data-providers)
27+
1028
## Installation
1129

1230
```bash
@@ -166,6 +184,27 @@ These methods save "current" state and unload each `Runtime mock` mock that was
166184

167185
Using `MockerState::saveState()` after `Mocker->load($mocks)` saves only **_Pre-defined_** mocks.
168186

187+
### Tracking calls
188+
189+
You may track calls of mocked functions by using `MockerState::getTraces()` method.
190+
191+
```php
192+
$traces = MockerState::getTraces('App\Service', 'time');
193+
```
194+
195+
`$traces` will contain an array of arrays with the following structure:
196+
197+
```php
198+
[
199+
[
200+
'arguments' => [], // arguments of the function
201+
'trace' => [], // the result of debug_backtrace function
202+
'result' => 1708764835, // result of the function
203+
],
204+
// ...
205+
]
206+
```
207+
169208
## Global namespaced functions
170209

171210
### Internal functions
@@ -186,6 +225,19 @@ The best way is to disable them only for tests by running a command with the add
186225
php -ddisable_functions=${functions} ./vendor/bin/phpunit
187226
```
188227

228+
> If you are using PHPStorm you may set the command in the `Run/Debug Configurations` section.
229+
> Add the flag `-ddisable_functions=${functions}` to the `Interpreter options` field.
230+
231+
> You may keep the command in the `composer.json` file under the `scripts` section.
232+
233+
```json
234+
{
235+
"scripts": {
236+
"test": "php -ddisable_functions=time,serialize,header,date ./vendor/bin/phpunit"
237+
}
238+
}
239+
```
240+
189241
> Replace `${functions}` with the list of functions that you want to mock, separated by commas, e.g.: `time,rand`.
190242
191243
So now you can mock global functions as well.

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,8 @@
2222
"psr-4": {
2323
"Xepozz\\InternalMocker\\Tests\\": "tests/"
2424
}
25+
},
26+
"scripts": {
27+
"test": "php -ddisable_functions=time vendor/bin/phpunit"
2528
}
2629
}

src/Mocker.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function generate(array $mocks): string
4242
$defaultString = $imock['default'] ? 'true' : 'false';
4343
$mockerConfig[] = <<<PHP
4444
MockerState::addCondition(
45-
"$namespace",
45+
"$namespace",
4646
"$functionName",
4747
$argumentsString,
4848
$resultString,
@@ -60,7 +60,7 @@ public function generate(array $mocks): string
6060
$outputs[] = <<<PHP
6161
namespace {$namespace} {
6262
use {$mockerConfigClassName};
63-
63+
6464
$innerOutputsString
6565
}
6666
PHP;
@@ -73,7 +73,7 @@ public function generate(array $mocks): string
7373
$pre = <<<PHP
7474
namespace {
7575
use {$mockerConfigClassName};
76-
76+
7777
{$runtimeMocks}
7878
}
7979
PHP;
@@ -115,10 +115,14 @@ private function generateFunction(mixed $groupedMocks): string
115115
$string = <<<PHP
116116
function $functionName(...\$arguments)
117117
{
118+
\$position = MockerState::saveTrace(__NAMESPACE__, "$functionName", \$arguments);
118119
if (MockerState::checkCondition(__NAMESPACE__, "$functionName", \$arguments)) {
119-
return MockerState::getResult(__NAMESPACE__, "$functionName", \$arguments);
120+
\$result = MockerState::getResult(__NAMESPACE__, "$functionName", \$arguments);
121+
} else {
122+
\$result = MockerState::getDefaultResult(__NAMESPACE__, "$functionName", $function);
120123
}
121-
return MockerState::getDefaultResult(__NAMESPACE__, "$functionName", $function);
124+
125+
return MockerState::saveTraceResult(__NAMESPACE__, "$functionName", \$position, \$result);
122126
}
123127
PHP;
124128
$innerOutputs[] = $string;
@@ -131,4 +135,4 @@ public function getConfigPath(): string
131135
{
132136
return $this->path;
133137
}
134-
}
138+
}

src/MockerState.php

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
final class MockerState
88
{
9+
private static mixed $traces = [];
910
private static array $savedState = [];
1011
private static array $state = [];
1112
private static array $defaults = [];
@@ -106,5 +107,38 @@ public static function saveState(): void
106107
public static function resetState(): void
107108
{
108109
self::$state = self::$savedState;
110+
self::$traces = [];
109111
}
110-
}
112+
113+
public static function saveTrace(
114+
string $namespace,
115+
string $functionName,
116+
array $arguments
117+
): int {
118+
$position = count(self::$traces[$namespace][$functionName] ?? []);
119+
self::$traces[$namespace][$functionName][$position] = [
120+
'arguments' => $arguments,
121+
'trace' => debug_backtrace(),
122+
];
123+
124+
return $position;
125+
}
126+
127+
public static function saveTraceResult(
128+
string $namespace,
129+
string $functionName,
130+
int $position,
131+
mixed $result
132+
): mixed {
133+
self::$traces[$namespace][$functionName][$position]['result'] = $result;
134+
135+
return $result;
136+
}
137+
138+
public static function getTraces(
139+
string $namespace,
140+
string $functionName
141+
): array {
142+
return self::$traces[$namespace][$functionName] ?? [];
143+
}
144+
}

tests/Integration/TraceTest.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Xepozz\InternalMocker\Tests\Integration;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Xepozz\InternalMocker\MockerState;
9+
use Xepozz\InternalMocker\Tests\MockerExtension;
10+
11+
final class TraceTest extends TestCase
12+
{
13+
public function __construct(?string $name = null, array $data = [], $dataName = '')
14+
{
15+
MockerExtension::load();
16+
parent::__construct($name, $data, $dataName);
17+
}
18+
19+
public function testLogs(): void
20+
{
21+
$object = new UseInSucceedDataProviderStub();
22+
$object->run('test');
23+
$object->run('test2');
24+
$object->run('test3');
25+
26+
$traces = MockerState::getTraces(
27+
__NAMESPACE__,
28+
'serialize',
29+
);
30+
31+
$this->assertIsArray($traces);
32+
33+
$this->assertCount(3, $traces);
34+
35+
foreach ($traces as $trace) {
36+
$this->assertArrayHasKey('arguments', $trace);
37+
$this->assertArrayHasKey('result', $trace);
38+
39+
$this->assertIsArray($trace['arguments']);
40+
$this->assertIsArray($trace['trace']);
41+
$this->assertIsString($trace['result']);
42+
}
43+
44+
$this->assertEquals(['test'], $traces[0]['arguments']);
45+
$this->assertEquals('s:4:"test";', $traces[0]['result']);
46+
47+
$this->assertEquals(['test2'], $traces[1]['arguments']);
48+
$this->assertEquals('s:5:"test2";', $traces[1]['result']);
49+
50+
$this->assertEquals(['test3'], $traces[2]['arguments']);
51+
$this->assertEquals('s:5:"test3";', $traces[2]['result']);
52+
}
53+
54+
public function testBacktrace(): void
55+
{
56+
$object = new UseInSucceedDataProviderStub();
57+
$object->run('test');
58+
59+
$traces = MockerState::getTraces(
60+
__NAMESPACE__,
61+
'serialize',
62+
);
63+
64+
$this->assertIsArray($traces);
65+
66+
$this->assertCount(1, $traces);
67+
68+
$this->assertEquals(['test'], $traces[0]['arguments']);
69+
$this->assertEquals('saveTrace', $traces[0]['trace'][0]['function']);
70+
$this->assertEquals(120, $traces[0]['trace'][0]['line']);
71+
$this->assertEquals(
72+
[
73+
__NAMESPACE__,
74+
'serialize',
75+
['test'],
76+
],
77+
$traces[0]['trace'][0]['args'],
78+
);
79+
80+
$this->assertEquals(__NAMESPACE__ . '\serialize', $traces[0]['trace'][1]['function']);
81+
$this->assertEquals(11, $traces[0]['trace'][1]['line']);
82+
$this->assertEquals(['test'], $traces[0]['trace'][1]['args']);
83+
}
84+
}

tests/MockerTest.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function generateProvider()
3535
use Xepozz\InternalMocker\MockerState;
3636
3737
MockerState::addCondition(
38-
"Xepozz\InternalMocker\Tests\Integration",
38+
"Xepozz\InternalMocker\Tests\Integration",
3939
"time",
4040
[],
4141
555,
@@ -49,10 +49,14 @@ public function generateProvider()
4949
5050
function time(...\$arguments)
5151
{
52+
\$position = MockerState::saveTrace(__NAMESPACE__, "time", \$arguments);
5253
if (MockerState::checkCondition(__NAMESPACE__, "time", \$arguments)) {
53-
return MockerState::getResult(__NAMESPACE__, "time", \$arguments);
54+
\$result = MockerState::getResult(__NAMESPACE__, "time", \$arguments);
55+
} else {
56+
\$result = MockerState::getDefaultResult(__NAMESPACE__, "time", fn() => \\time(...\$arguments));
5457
}
55-
return MockerState::getDefaultResult(__NAMESPACE__, "time", fn() => \\time(...\$arguments));
58+
59+
return MockerState::saveTraceResult(__NAMESPACE__, "time", \$position, \$result);
5660
}
5761
}
5862
PHP,
@@ -83,14 +87,14 @@ function time(...\$arguments)
8387
use Xepozz\InternalMocker\MockerState;
8488
8589
MockerState::addCondition(
86-
"Xepozz\InternalMocker\Tests\Integration",
90+
"Xepozz\InternalMocker\Tests\Integration",
8791
"str_contains",
8892
['haystack' => 'string','needle' => 'str'],
8993
false,
9094
false,
9195
);
9296
MockerState::addCondition(
93-
"Xepozz\InternalMocker\Tests\Integration",
97+
"Xepozz\InternalMocker\Tests\Integration",
9498
"str_contains",
9599
['haystack' => 'string2','needle' => 'str'],
96100
false,
@@ -104,10 +108,14 @@ function time(...\$arguments)
104108
105109
function str_contains(...\$arguments)
106110
{
111+
\$position = MockerState::saveTrace(__NAMESPACE__, "str_contains", \$arguments);
107112
if (MockerState::checkCondition(__NAMESPACE__, "str_contains", \$arguments)) {
108-
return MockerState::getResult(__NAMESPACE__, "str_contains", \$arguments);
113+
\$result = MockerState::getResult(__NAMESPACE__, "str_contains", \$arguments);
114+
} else {
115+
\$result = MockerState::getDefaultResult(__NAMESPACE__, "str_contains", fn() => \\str_contains(...\$arguments));
109116
}
110-
return MockerState::getDefaultResult(__NAMESPACE__, "str_contains", fn() => \\str_contains(...\$arguments));
117+
118+
return MockerState::saveTraceResult(__NAMESPACE__, "str_contains", \$position, \$result);
111119
}
112120
}
113121
PHP,

0 commit comments

Comments
 (0)