Chris@0
|
1 # Prophecy
|
Chris@0
|
2
|
Chris@0
|
3 [](https://packagist.org/packages/phpspec/prophecy)
|
Chris@0
|
4 [](https://travis-ci.org/phpspec/prophecy)
|
Chris@0
|
5
|
Chris@0
|
6 Prophecy is a highly opinionated yet very powerful and flexible PHP object mocking
|
Chris@0
|
7 framework. Though initially it was created to fulfil phpspec2 needs, it is flexible
|
Chris@0
|
8 enough to be used inside any testing framework out there with minimal effort.
|
Chris@0
|
9
|
Chris@0
|
10 ## A simple example
|
Chris@0
|
11
|
Chris@0
|
12 ```php
|
Chris@0
|
13 <?php
|
Chris@0
|
14
|
Chris@0
|
15 class UserTest extends PHPUnit_Framework_TestCase
|
Chris@0
|
16 {
|
Chris@0
|
17 private $prophet;
|
Chris@0
|
18
|
Chris@0
|
19 public function testPasswordHashing()
|
Chris@0
|
20 {
|
Chris@0
|
21 $hasher = $this->prophet->prophesize('App\Security\Hasher');
|
Chris@0
|
22 $user = new App\Entity\User($hasher->reveal());
|
Chris@0
|
23
|
Chris@0
|
24 $hasher->generateHash($user, 'qwerty')->willReturn('hashed_pass');
|
Chris@0
|
25
|
Chris@0
|
26 $user->setPassword('qwerty');
|
Chris@0
|
27
|
Chris@0
|
28 $this->assertEquals('hashed_pass', $user->getPassword());
|
Chris@0
|
29 }
|
Chris@0
|
30
|
Chris@0
|
31 protected function setup()
|
Chris@0
|
32 {
|
Chris@0
|
33 $this->prophet = new \Prophecy\Prophet;
|
Chris@0
|
34 }
|
Chris@0
|
35
|
Chris@0
|
36 protected function tearDown()
|
Chris@0
|
37 {
|
Chris@0
|
38 $this->prophet->checkPredictions();
|
Chris@0
|
39 }
|
Chris@0
|
40 }
|
Chris@0
|
41 ```
|
Chris@0
|
42
|
Chris@0
|
43 ## Installation
|
Chris@0
|
44
|
Chris@0
|
45 ### Prerequisites
|
Chris@0
|
46
|
Chris@0
|
47 Prophecy requires PHP 5.3.3 or greater.
|
Chris@0
|
48
|
Chris@0
|
49 ### Setup through composer
|
Chris@0
|
50
|
Chris@0
|
51 First, add Prophecy to the list of dependencies inside your `composer.json`:
|
Chris@0
|
52
|
Chris@0
|
53 ```json
|
Chris@0
|
54 {
|
Chris@0
|
55 "require-dev": {
|
Chris@0
|
56 "phpspec/prophecy": "~1.0"
|
Chris@0
|
57 }
|
Chris@0
|
58 }
|
Chris@0
|
59 ```
|
Chris@0
|
60
|
Chris@0
|
61 Then simply install it with composer:
|
Chris@0
|
62
|
Chris@0
|
63 ```bash
|
Chris@0
|
64 $> composer install --prefer-dist
|
Chris@0
|
65 ```
|
Chris@0
|
66
|
Chris@0
|
67 You can read more about Composer on its [official webpage](http://getcomposer.org).
|
Chris@0
|
68
|
Chris@0
|
69 ## How to use it
|
Chris@0
|
70
|
Chris@0
|
71 First of all, in Prophecy every word has a logical meaning, even the name of the library
|
Chris@0
|
72 itself (Prophecy). When you start feeling that, you'll become very fluid with this
|
Chris@0
|
73 tool.
|
Chris@0
|
74
|
Chris@0
|
75 For example, Prophecy has been named that way because it concentrates on describing the future
|
Chris@0
|
76 behavior of objects with very limited knowledge about them. But as with any other prophecy,
|
Chris@0
|
77 those object prophecies can't create themselves - there should be a Prophet:
|
Chris@0
|
78
|
Chris@0
|
79 ```php
|
Chris@0
|
80 $prophet = new Prophecy\Prophet;
|
Chris@0
|
81 ```
|
Chris@0
|
82
|
Chris@0
|
83 The Prophet creates prophecies by *prophesizing* them:
|
Chris@0
|
84
|
Chris@0
|
85 ```php
|
Chris@0
|
86 $prophecy = $prophet->prophesize();
|
Chris@0
|
87 ```
|
Chris@0
|
88
|
Chris@0
|
89 The result of the `prophesize()` method call is a new object of class `ObjectProphecy`. Yes,
|
Chris@0
|
90 that's your specific object prophecy, which describes how your object would behave
|
Chris@0
|
91 in the near future. But first, you need to specify which object you're talking about,
|
Chris@0
|
92 right?
|
Chris@0
|
93
|
Chris@0
|
94 ```php
|
Chris@0
|
95 $prophecy->willExtend('stdClass');
|
Chris@0
|
96 $prophecy->willImplement('SessionHandlerInterface');
|
Chris@0
|
97 ```
|
Chris@0
|
98
|
Chris@0
|
99 There are 2 interesting calls - `willExtend` and `willImplement`. The first one tells
|
Chris@0
|
100 object prophecy that our object should extend specific class, the second one says that
|
Chris@0
|
101 it should implement some interface. Obviously, objects in PHP can implement multiple
|
Chris@0
|
102 interfaces, but extend only one parent class.
|
Chris@0
|
103
|
Chris@0
|
104 ### Dummies
|
Chris@0
|
105
|
Chris@0
|
106 Ok, now we have our object prophecy. What can we do with it? First of all, we can get
|
Chris@0
|
107 our object *dummy* by revealing its prophecy:
|
Chris@0
|
108
|
Chris@0
|
109 ```php
|
Chris@0
|
110 $dummy = $prophecy->reveal();
|
Chris@0
|
111 ```
|
Chris@0
|
112
|
Chris@0
|
113 The `$dummy` variable now holds a special dummy object. Dummy objects are objects that extend
|
Chris@0
|
114 and/or implement preset classes/interfaces by overriding all their public methods. The key
|
Chris@0
|
115 point about dummies is that they do not hold any logic - they just do nothing. Any method
|
Chris@0
|
116 of the dummy will always return `null` and the dummy will never throw any exceptions.
|
Chris@0
|
117 Dummy is your friend if you don't care about the actual behavior of this double and just need
|
Chris@0
|
118 a token object to satisfy a method typehint.
|
Chris@0
|
119
|
Chris@0
|
120 You need to understand one thing - a dummy is not a prophecy. Your object prophecy is still
|
Chris@0
|
121 assigned to `$prophecy` variable and in order to manipulate with your expectations, you
|
Chris@0
|
122 should work with it. `$dummy` is a dummy - a simple php object that tries to fulfil your
|
Chris@0
|
123 prophecy.
|
Chris@0
|
124
|
Chris@0
|
125 ### Stubs
|
Chris@0
|
126
|
Chris@0
|
127 Ok, now we know how to create basic prophecies and reveal dummies from them. That's
|
Chris@0
|
128 awesome if we don't care about our _doubles_ (objects that reflect originals)
|
Chris@0
|
129 interactions. If we do, we need to use *stubs* or *mocks*.
|
Chris@0
|
130
|
Chris@0
|
131 A stub is an object double, which doesn't have any expectations about the object behavior,
|
Chris@0
|
132 but when put in specific environment, behaves in specific way. Ok, I know, it's cryptic,
|
Chris@0
|
133 but bear with me for a minute. Simply put, a stub is a dummy, which depending on the called
|
Chris@0
|
134 method signature does different things (has logic). To create stubs in Prophecy:
|
Chris@0
|
135
|
Chris@0
|
136 ```php
|
Chris@0
|
137 $prophecy->read('123')->willReturn('value');
|
Chris@0
|
138 ```
|
Chris@0
|
139
|
Chris@0
|
140 Oh wow. We've just made an arbitrary call on the object prophecy? Yes, we did. And this
|
Chris@0
|
141 call returned us a new object instance of class `MethodProphecy`. Yep, that's a specific
|
Chris@0
|
142 method with arguments prophecy. Method prophecies give you the ability to create method
|
Chris@0
|
143 promises or predictions. We'll talk about method predictions later in the _Mocks_ section.
|
Chris@0
|
144
|
Chris@0
|
145 #### Promises
|
Chris@0
|
146
|
Chris@0
|
147 Promises are logical blocks, that represent your fictional methods in prophecy terms
|
Chris@0
|
148 and they are handled by the `MethodProphecy::will(PromiseInterface $promise)` method.
|
Chris@0
|
149 As a matter of fact, the call that we made earlier (`willReturn('value')`) is a simple
|
Chris@0
|
150 shortcut to:
|
Chris@0
|
151
|
Chris@0
|
152 ```php
|
Chris@0
|
153 $prophecy->read('123')->will(new Prophecy\Promise\ReturnPromise(array('value')));
|
Chris@0
|
154 ```
|
Chris@0
|
155
|
Chris@0
|
156 This promise will cause any call to our double's `read()` method with exactly one
|
Chris@0
|
157 argument - `'123'` to always return `'value'`. But that's only for this
|
Chris@0
|
158 promise, there's plenty others you can use:
|
Chris@0
|
159
|
Chris@0
|
160 - `ReturnPromise` or `->willReturn(1)` - returns a value from a method call
|
Chris@0
|
161 - `ReturnArgumentPromise` or `->willReturnArgument($index)` - returns the nth method argument from call
|
Chris@12
|
162 - `ThrowPromise` or `->willThrow($exception)` - causes the method to throw specific exception
|
Chris@0
|
163 - `CallbackPromise` or `->will($callback)` - gives you a quick way to define your own custom logic
|
Chris@0
|
164
|
Chris@0
|
165 Keep in mind, that you can always add even more promises by implementing
|
Chris@0
|
166 `Prophecy\Promise\PromiseInterface`.
|
Chris@0
|
167
|
Chris@0
|
168 #### Method prophecies idempotency
|
Chris@0
|
169
|
Chris@0
|
170 Prophecy enforces same method prophecies and, as a consequence, same promises and
|
Chris@0
|
171 predictions for the same method calls with the same arguments. This means:
|
Chris@0
|
172
|
Chris@0
|
173 ```php
|
Chris@0
|
174 $methodProphecy1 = $prophecy->read('123');
|
Chris@0
|
175 $methodProphecy2 = $prophecy->read('123');
|
Chris@0
|
176 $methodProphecy3 = $prophecy->read('321');
|
Chris@0
|
177
|
Chris@0
|
178 $methodProphecy1 === $methodProphecy2;
|
Chris@0
|
179 $methodProphecy1 !== $methodProphecy3;
|
Chris@0
|
180 ```
|
Chris@0
|
181
|
Chris@0
|
182 That's interesting, right? Now you might ask me how would you define more complex
|
Chris@0
|
183 behaviors where some method call changes behavior of others. In PHPUnit or Mockery
|
Chris@0
|
184 you do that by predicting how many times your method will be called. In Prophecy,
|
Chris@0
|
185 you'll use promises for that:
|
Chris@0
|
186
|
Chris@0
|
187 ```php
|
Chris@0
|
188 $user->getName()->willReturn(null);
|
Chris@0
|
189
|
Chris@0
|
190 // For PHP 5.4
|
Chris@0
|
191 $user->setName('everzet')->will(function () {
|
Chris@0
|
192 $this->getName()->willReturn('everzet');
|
Chris@0
|
193 });
|
Chris@0
|
194
|
Chris@0
|
195 // For PHP 5.3
|
Chris@0
|
196 $user->setName('everzet')->will(function ($args, $user) {
|
Chris@0
|
197 $user->getName()->willReturn('everzet');
|
Chris@0
|
198 });
|
Chris@0
|
199
|
Chris@0
|
200 // Or
|
Chris@0
|
201 $user->setName('everzet')->will(function ($args) use ($user) {
|
Chris@0
|
202 $user->getName()->willReturn('everzet');
|
Chris@0
|
203 });
|
Chris@0
|
204 ```
|
Chris@0
|
205
|
Chris@0
|
206 And now it doesn't matter how many times or in which order your methods are called.
|
Chris@0
|
207 What matters is their behaviors and how well you faked it.
|
Chris@0
|
208
|
Chris@0
|
209 #### Arguments wildcarding
|
Chris@0
|
210
|
Chris@0
|
211 The previous example is awesome (at least I hope it is for you), but that's not
|
Chris@0
|
212 optimal enough. We hardcoded `'everzet'` in our expectation. Isn't there a better
|
Chris@0
|
213 way? In fact there is, but it involves understanding what this `'everzet'`
|
Chris@0
|
214 actually is.
|
Chris@0
|
215
|
Chris@0
|
216 You see, even if method arguments used during method prophecy creation look
|
Chris@0
|
217 like simple method arguments, in reality they are not. They are argument token
|
Chris@0
|
218 wildcards. As a matter of fact, `->setName('everzet')` looks like a simple call just
|
Chris@0
|
219 because Prophecy automatically transforms it under the hood into:
|
Chris@0
|
220
|
Chris@0
|
221 ```php
|
Chris@0
|
222 $user->setName(new Prophecy\Argument\Token\ExactValueToken('everzet'));
|
Chris@0
|
223 ```
|
Chris@0
|
224
|
Chris@0
|
225 Those argument tokens are simple PHP classes, that implement
|
Chris@0
|
226 `Prophecy\Argument\Token\TokenInterface` and tell Prophecy how to compare real arguments
|
Chris@0
|
227 with your expectations. And yes, those classnames are damn big. That's why there's a
|
Chris@0
|
228 shortcut class `Prophecy\Argument`, which you can use to create tokens like that:
|
Chris@0
|
229
|
Chris@0
|
230 ```php
|
Chris@0
|
231 use Prophecy\Argument;
|
Chris@0
|
232
|
Chris@0
|
233 $user->setName(Argument::exact('everzet'));
|
Chris@0
|
234 ```
|
Chris@0
|
235
|
Chris@0
|
236 `ExactValueToken` is not very useful in our case as it forced us to hardcode the username.
|
Chris@0
|
237 That's why Prophecy comes bundled with a bunch of other tokens:
|
Chris@0
|
238
|
Chris@0
|
239 - `IdenticalValueToken` or `Argument::is($value)` - checks that the argument is identical to a specific value
|
Chris@0
|
240 - `ExactValueToken` or `Argument::exact($value)` - checks that the argument matches a specific value
|
Chris@0
|
241 - `TypeToken` or `Argument::type($typeOrClass)` - checks that the argument matches a specific type or
|
Chris@0
|
242 classname
|
Chris@0
|
243 - `ObjectStateToken` or `Argument::which($method, $value)` - checks that the argument method returns
|
Chris@0
|
244 a specific value
|
Chris@0
|
245 - `CallbackToken` or `Argument::that(callback)` - checks that the argument matches a custom callback
|
Chris@0
|
246 - `AnyValueToken` or `Argument::any()` - matches any argument
|
Chris@0
|
247 - `AnyValuesToken` or `Argument::cetera()` - matches any arguments to the rest of the signature
|
Chris@0
|
248 - `StringContainsToken` or `Argument::containingString($value)` - checks that the argument contains a specific string value
|
Chris@0
|
249
|
Chris@0
|
250 And you can add even more by implementing `TokenInterface` with your own custom classes.
|
Chris@0
|
251
|
Chris@0
|
252 So, let's refactor our initial `{set,get}Name()` logic with argument tokens:
|
Chris@0
|
253
|
Chris@0
|
254 ```php
|
Chris@0
|
255 use Prophecy\Argument;
|
Chris@0
|
256
|
Chris@0
|
257 $user->getName()->willReturn(null);
|
Chris@0
|
258
|
Chris@0
|
259 // For PHP 5.4
|
Chris@0
|
260 $user->setName(Argument::type('string'))->will(function ($args) {
|
Chris@0
|
261 $this->getName()->willReturn($args[0]);
|
Chris@0
|
262 });
|
Chris@0
|
263
|
Chris@0
|
264 // For PHP 5.3
|
Chris@0
|
265 $user->setName(Argument::type('string'))->will(function ($args, $user) {
|
Chris@0
|
266 $user->getName()->willReturn($args[0]);
|
Chris@0
|
267 });
|
Chris@0
|
268
|
Chris@0
|
269 // Or
|
Chris@0
|
270 $user->setName(Argument::type('string'))->will(function ($args) use ($user) {
|
Chris@0
|
271 $user->getName()->willReturn($args[0]);
|
Chris@0
|
272 });
|
Chris@0
|
273 ```
|
Chris@0
|
274
|
Chris@0
|
275 That's it. Now our `{set,get}Name()` prophecy will work with any string argument provided to it.
|
Chris@0
|
276 We've just described how our stub object should behave, even though the original object could have
|
Chris@0
|
277 no behavior whatsoever.
|
Chris@0
|
278
|
Chris@0
|
279 One last bit about arguments now. You might ask, what happens in case of:
|
Chris@0
|
280
|
Chris@0
|
281 ```php
|
Chris@0
|
282 use Prophecy\Argument;
|
Chris@0
|
283
|
Chris@0
|
284 $user->getName()->willReturn(null);
|
Chris@0
|
285
|
Chris@0
|
286 // For PHP 5.4
|
Chris@0
|
287 $user->setName(Argument::type('string'))->will(function ($args) {
|
Chris@0
|
288 $this->getName()->willReturn($args[0]);
|
Chris@0
|
289 });
|
Chris@0
|
290
|
Chris@0
|
291 // For PHP 5.3
|
Chris@0
|
292 $user->setName(Argument::type('string'))->will(function ($args, $user) {
|
Chris@0
|
293 $user->getName()->willReturn($args[0]);
|
Chris@0
|
294 });
|
Chris@0
|
295
|
Chris@0
|
296 // Or
|
Chris@0
|
297 $user->setName(Argument::type('string'))->will(function ($args) use ($user) {
|
Chris@0
|
298 $user->getName()->willReturn($args[0]);
|
Chris@0
|
299 });
|
Chris@0
|
300
|
Chris@0
|
301 $user->setName(Argument::any())->will(function () {
|
Chris@0
|
302 });
|
Chris@0
|
303 ```
|
Chris@0
|
304
|
Chris@0
|
305 Nothing. Your stub will continue behaving the way it did before. That's because of how
|
Chris@0
|
306 arguments wildcarding works. Every argument token type has a different score level, which
|
Chris@0
|
307 wildcard then uses to calculate the final arguments match score and use the method prophecy
|
Chris@0
|
308 promise that has the highest score. In this case, `Argument::type()` in case of success
|
Chris@0
|
309 scores `5` and `Argument::any()` scores `3`. So the type token wins, as does the first
|
Chris@0
|
310 `setName()` method prophecy and its promise. The simple rule of thumb - more precise token
|
Chris@0
|
311 always wins.
|
Chris@0
|
312
|
Chris@0
|
313 #### Getting stub objects
|
Chris@0
|
314
|
Chris@0
|
315 Ok, now we know how to define our prophecy method promises, let's get our stub from
|
Chris@0
|
316 it:
|
Chris@0
|
317
|
Chris@0
|
318 ```php
|
Chris@0
|
319 $stub = $prophecy->reveal();
|
Chris@0
|
320 ```
|
Chris@0
|
321
|
Chris@0
|
322 As you might see, the only difference between how we get dummies and stubs is that with
|
Chris@0
|
323 stubs we describe every object conversation instead of just agreeing with `null` returns
|
Chris@0
|
324 (object being *dummy*). As a matter of fact, after you define your first promise
|
Chris@0
|
325 (method call), Prophecy will force you to define all the communications - it throws
|
Chris@0
|
326 the `UnexpectedCallException` for any call you didn't describe with object prophecy before
|
Chris@0
|
327 calling it on a stub.
|
Chris@0
|
328
|
Chris@0
|
329 ### Mocks
|
Chris@0
|
330
|
Chris@0
|
331 Now we know how to define doubles without behavior (dummies) and doubles with behavior, but
|
Chris@0
|
332 no expectations (stubs). What's left is doubles for which we have some expectations. These
|
Chris@0
|
333 are called mocks and in Prophecy they look almost exactly the same as stubs, except that
|
Chris@0
|
334 they define *predictions* instead of *promises* on method prophecies:
|
Chris@0
|
335
|
Chris@0
|
336 ```php
|
Chris@0
|
337 $entityManager->flush()->shouldBeCalled();
|
Chris@0
|
338 ```
|
Chris@0
|
339
|
Chris@0
|
340 #### Predictions
|
Chris@0
|
341
|
Chris@0
|
342 The `shouldBeCalled()` method here assigns `CallPrediction` to our method prophecy.
|
Chris@0
|
343 Predictions are a delayed behavior check for your prophecies. You see, during the entire lifetime
|
Chris@0
|
344 of your doubles, Prophecy records every single call you're making against it inside your
|
Chris@0
|
345 code. After that, Prophecy can use this collected information to check if it matches defined
|
Chris@0
|
346 predictions. You can assign predictions to method prophecies using the
|
Chris@0
|
347 `MethodProphecy::should(PredictionInterface $prediction)` method. As a matter of fact,
|
Chris@0
|
348 the `shouldBeCalled()` method we used earlier is just a shortcut to:
|
Chris@0
|
349
|
Chris@0
|
350 ```php
|
Chris@0
|
351 $entityManager->flush()->should(new Prophecy\Prediction\CallPrediction());
|
Chris@0
|
352 ```
|
Chris@0
|
353
|
Chris@0
|
354 It checks if your method of interest (that matches both the method name and the arguments wildcard)
|
Chris@0
|
355 was called 1 or more times. If the prediction failed then it throws an exception. When does this
|
Chris@0
|
356 check happen? Whenever you call `checkPredictions()` on the main Prophet object:
|
Chris@0
|
357
|
Chris@0
|
358 ```php
|
Chris@0
|
359 $prophet->checkPredictions();
|
Chris@0
|
360 ```
|
Chris@0
|
361
|
Chris@0
|
362 In PHPUnit, you would want to put this call into the `tearDown()` method. If no predictions
|
Chris@0
|
363 are defined, it would do nothing. So it won't harm to call it after every test.
|
Chris@0
|
364
|
Chris@0
|
365 There are plenty more predictions you can play with:
|
Chris@0
|
366
|
Chris@0
|
367 - `CallPrediction` or `shouldBeCalled()` - checks that the method has been called 1 or more times
|
Chris@0
|
368 - `NoCallsPrediction` or `shouldNotBeCalled()` - checks that the method has not been called
|
Chris@0
|
369 - `CallTimesPrediction` or `shouldBeCalledTimes($count)` - checks that the method has been called
|
Chris@0
|
370 `$count` times
|
Chris@0
|
371 - `CallbackPrediction` or `should($callback)` - checks the method against your own custom callback
|
Chris@0
|
372
|
Chris@0
|
373 Of course, you can always create your own custom prediction any time by implementing
|
Chris@0
|
374 `PredictionInterface`.
|
Chris@0
|
375
|
Chris@0
|
376 ### Spies
|
Chris@0
|
377
|
Chris@0
|
378 The last bit of awesomeness in Prophecy is out-of-the-box spies support. As I said in the previous
|
Chris@0
|
379 section, Prophecy records every call made during the double's entire lifetime. This means
|
Chris@0
|
380 you don't need to record predictions in order to check them. You can also do it
|
Chris@0
|
381 manually by using the `MethodProphecy::shouldHave(PredictionInterface $prediction)` method:
|
Chris@0
|
382
|
Chris@0
|
383 ```php
|
Chris@0
|
384 $em = $prophet->prophesize('Doctrine\ORM\EntityManager');
|
Chris@0
|
385
|
Chris@0
|
386 $controller->createUser($em->reveal());
|
Chris@0
|
387
|
Chris@0
|
388 $em->flush()->shouldHaveBeenCalled();
|
Chris@0
|
389 ```
|
Chris@0
|
390
|
Chris@0
|
391 Such manipulation with doubles is called spying. And with Prophecy it just works.
|