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