Projekt

Obecné

Profil

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