1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Ekvedaras\LaravelTestHelpers\Traits\Helpers; |
6
|
|
|
|
7
|
|
|
use Mockery; |
8
|
|
|
use ReflectionClass; |
9
|
|
|
use ReflectionException; |
10
|
|
|
use Mockery\Instantiator; |
11
|
|
|
use Mockery\MockInterface; |
12
|
|
|
use Illuminate\Foundation\Application; |
13
|
|
|
use PHPUnit\Framework\MockObject\MockObject; |
14
|
|
|
use PHPUnit_Framework_MockObject_MockObject; |
15
|
|
|
use PHPUnit\Framework\MockObject\MockBuilder; |
16
|
|
|
use Ekvedaras\LaravelTestHelpers\Helpers\TestHelpersMock; |
17
|
|
|
use Ekvedaras\LaravelTestHelpers\Exceptions\MockInjectionException; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Trait BuildsMocks. |
21
|
|
|
* |
22
|
|
|
* @property-read Application $app |
23
|
|
|
* @method MockBuilder getMockBuilder(string $mockClass) |
24
|
|
|
*/ |
25
|
|
|
trait BuildsMocks |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* @param string $mockClass |
29
|
|
|
* @param string|null $injectorClass |
30
|
|
|
* @param array|null|bool $methods |
31
|
|
|
* @param array|null $constructorArgs |
32
|
|
|
* @param bool $onlyForInjector |
33
|
|
|
* @return PHPUnit_Framework_MockObject_MockObject|TestHelpersMock |
34
|
|
|
*/ |
35
|
|
|
protected function mock( |
36
|
|
|
string $mockClass, |
37
|
|
|
string $injectorClass = null, |
38
|
|
|
$methods = false, |
39
|
|
|
array $constructorArgs = null, |
40
|
|
|
bool $onlyForInjector = false |
41
|
|
|
): PHPUnit_Framework_MockObject_MockObject { |
42
|
|
|
$this->forgetInstances($mockClass, $injectorClass); |
43
|
|
|
$mock = $this->createPHPUnitMock($mockClass, $constructorArgs, $methods); |
44
|
|
|
$this->injectMockToLaravel($mockClass, $mock, $onlyForInjector, $injectorClass); |
45
|
|
|
|
46
|
|
|
return $mock; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @param string $mockClass |
51
|
|
|
* @param string|null $injectorClass |
52
|
|
|
* @param bool $onlyForInjector |
53
|
|
|
* @return MockInterface |
54
|
|
|
*/ |
55
|
|
|
protected function mockery( |
56
|
|
|
string $mockClass, |
57
|
|
|
string $injectorClass = null, |
58
|
|
|
bool $onlyForInjector = false |
59
|
|
|
): MockInterface { |
60
|
|
|
$this->forgetInstances($mockClass, $injectorClass); |
61
|
|
|
$mock = Mockery::mock($mockClass); |
62
|
|
|
$this->injectMockToLaravel($mockClass, $mock, $onlyForInjector, $injectorClass); |
63
|
|
|
|
64
|
|
|
return $mock; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @param string $mockClass |
69
|
|
|
* @param string|null $injectorClass |
70
|
|
|
* @param bool $onlyForInjector |
71
|
|
|
* @return MockInterface |
72
|
|
|
*/ |
73
|
|
|
protected function spy( |
74
|
|
|
string $mockClass, |
75
|
|
|
string $injectorClass = null, |
76
|
|
|
bool $onlyForInjector = false |
77
|
|
|
): MockInterface { |
78
|
|
|
$this->forgetInstances($mockClass, $injectorClass); |
79
|
|
|
$mock = Mockery::spy($mockClass); |
80
|
|
|
$this->injectMockToLaravel($mockClass, $mock, $onlyForInjector, $injectorClass); |
81
|
|
|
|
82
|
|
|
return $mock; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @param string $mockClass |
87
|
|
|
* @param string|null $injectorClass |
88
|
|
|
*/ |
89
|
|
|
private function forgetInstances(string $mockClass, string $injectorClass = null): void |
90
|
|
|
{ |
91
|
|
|
$this->app->forgetInstance($mockClass); |
92
|
|
|
|
93
|
|
|
if (isset($injectorClass)) { |
94
|
|
|
$this->app->forgetInstance($injectorClass); |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @param string $mockClass |
100
|
|
|
* @param array|null $constructorArgs |
101
|
|
|
* @param array|null|bool $methods |
102
|
|
|
* @return PHPUnit_Framework_MockObject_MockObject|TestHelpersMock |
103
|
|
|
* @throws ReflectionException |
104
|
|
|
*/ |
105
|
|
|
private function createPHPUnitMock( |
106
|
|
|
string $mockClass, |
107
|
|
|
array $constructorArgs = null, |
108
|
|
|
$methods = false |
109
|
|
|
): PHPUnit_Framework_MockObject_MockObject { |
110
|
|
|
$builder = $this->getMockBuilder($mockClass); |
111
|
|
|
|
112
|
|
|
if (isset($constructorArgs)) { |
113
|
|
|
$builder->setConstructorArgs($constructorArgs); |
114
|
|
|
} else { |
115
|
|
|
$builder->disableOriginalConstructor(); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
if ($methods !== false) { |
119
|
|
|
$builder->setMethods($methods); |
|
|
|
|
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
$mock = $builder->getMock(); |
123
|
|
|
$mockedClass = get_class($mock); |
124
|
|
|
$helperClass = $this->getClassShortName(TestHelpersMock::class); |
125
|
|
|
$wrapperClass = $helperClass.'_'.$mockedClass.'_'.str_random(); |
126
|
|
|
|
127
|
|
|
$template = file_get_contents(__DIR__.'/../../Helpers/TestHelpersMock.php'); |
128
|
|
|
$template = str_replace("class $helperClass", "class $wrapperClass extends $mockedClass", $template); |
129
|
|
|
$template = substr($template, strpos($template, "class $wrapperClass")); |
130
|
|
|
|
131
|
|
|
return $this->getObject( |
132
|
|
|
$template, |
133
|
|
|
$wrapperClass, |
134
|
|
|
$mock, |
135
|
|
|
$mockClass, |
136
|
|
|
! is_null($constructorArgs), |
137
|
|
|
(array) $constructorArgs |
138
|
|
|
); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @param string $code |
143
|
|
|
* @param string $className |
144
|
|
|
* @param object $proxyTarget |
145
|
|
|
* @param array|string $type |
146
|
|
|
* @param bool $callOriginalConstructor |
147
|
|
|
* @param array $arguments |
148
|
|
|
* @param bool $callAutoload |
149
|
|
|
* @param bool $returnValueGeneration |
150
|
|
|
* |
151
|
|
|
* @return TestHelpersMock|MockObject |
152
|
|
|
* @throws ReflectionException |
153
|
|
|
*/ |
154
|
|
|
private function getObject( |
155
|
|
|
string $code, |
156
|
|
|
string $className, |
157
|
|
|
$proxyTarget, |
158
|
|
|
string $type = '', |
159
|
|
|
bool $callOriginalConstructor = false, |
160
|
|
|
array $arguments = [], |
161
|
|
|
bool $callAutoload = false, |
162
|
|
|
bool $returnValueGeneration = true |
163
|
|
|
) { |
164
|
|
|
$this->evalClass($code, $className); |
165
|
|
|
|
166
|
|
|
if ($callOriginalConstructor && |
167
|
|
|
is_string($type) && |
168
|
|
|
! interface_exists($type, $callAutoload)) { |
169
|
|
|
if (count($arguments) === 0) { |
170
|
|
|
$object = new $className; |
171
|
|
|
} else { |
172
|
|
|
$class = new ReflectionClass($className); |
173
|
|
|
$object = $class->newInstanceArgs($arguments); |
174
|
|
|
} |
175
|
|
|
} else { |
176
|
|
|
$instantiator = new Instantiator; |
177
|
|
|
$object = $instantiator->instantiate($className); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
$object->__phpunit_setOriginalObject($proxyTarget); |
181
|
|
|
|
182
|
|
|
if ($object instanceof MockObject) { |
183
|
|
|
$object->__phpunit_setReturnValueGeneration($returnValueGeneration); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
return $object; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* @param string $code |
191
|
|
|
* @param string $className |
192
|
|
|
*/ |
193
|
|
|
private function evalClass(string $code, string $className): void |
194
|
|
|
{ |
195
|
|
|
if (! class_exists($className, false)) { |
196
|
|
|
eval($code); |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @param string $mockClass |
202
|
|
|
* @param PHPUnit_Framework_MockObject_MockObject|MockInterface $mock |
203
|
|
|
* @param bool $onlyForInjector |
204
|
|
|
* @param string|null $injectorClass |
205
|
|
|
* @throws MockInjectionException |
206
|
|
|
*/ |
207
|
|
|
private function injectMockToLaravel( |
208
|
|
|
string $mockClass, |
209
|
|
|
$mock, |
210
|
|
|
bool $onlyForInjector = false, |
211
|
|
|
string $injectorClass = null |
212
|
|
|
): void { |
213
|
|
|
if ($onlyForInjector) { |
214
|
|
|
if (! isset($injectorClass)) { |
215
|
|
|
throw MockInjectionException::injectorNotGiven(); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
$this->app->when($injectorClass)->needs($mockClass)->give(function () use ($mock) { |
219
|
|
|
return $mock; |
220
|
|
|
}); |
221
|
|
|
} else { |
222
|
|
|
$this->app->instance($mockClass, $mock); |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* @param string $class |
228
|
|
|
* @return string |
229
|
|
|
*/ |
230
|
|
|
private function getClassShortName(string $class): string |
231
|
|
|
{ |
232
|
|
|
return substr(strrchr($class, '\\'), 1); |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.