Table of Contents
The Phake::when() method is used to stub methods in Phake. As discussed in the introduction,
stubbing allows an object method to be forced to return a particular value given a set of parameters. Similarly to
Phake::verify(), Phake::when() accepts a mock object generated from
Phake::mock() as its first parameter.
Imagine I was in the process of building the next great online shopping cart. The first thing any
good shopping cart allows is to be able to add items. The most important thing I want to know from
the shopping cart is how much money in merchandise is in there. So, I need to make myself a
ShoppingCart class. I also am going to need some class to define my items.
I am more worried about the money right now and because of that I am keenly aware that any item
in a shopping cart is going to have a price. So I will just create an interface to represent those
items called Item. Now take a minute to marvel at the creativity of those
names. Great, now check out the initial definitions for my objects.
Example 2.1. Cool Cart
<?php /** * An item that is going to make me rich. */ interface Item { /** * @return money */ public function getPrice(); } /** * A customer's cart that will contain items that are going to make me rich. */ class ShoppingCart { private $items = array(); /** * Adds an item to the customer's order * @param Item $item */ public function addItem(Item $item) { $this->items[] = $item; } /** * Returns the current sub total of the customer's order * @return money */ public function getSubTotal() { } } ?>
So, I am furiously coding away at this fantastic new ShoppingCart class when I
realize, I am doing it wrong! You see, a few years ago I went to this conference with a bunch of
other geeky people to talk about how to make quality software. I am supposed to be writing unit
tests. Here I am, a solid thirteen lines (not counting comments) of code into my awe inspiring
new software and I haven't written a single test. I tell myself, "There's no better time to change
than right now!" So I decide to start testing. After looking at the options I decide PHPUnit with
this sweet new mock library called Phake is the way to go.
My first test is going to be for the currently unimplemented ShoppingCart::getSubTotal()
method. I already have a pretty good idea of what this function is going to need to do. It will
need to look at all of the items in the cart, retrieve their price, add it all together and return
the result. So, in my test I know I am going to need a fixture that sets up a shopping cart with
a few items added. Then I am going to need a test that calls ShoppingCart::getSubTotal()
and asserts that it returns a value equal to the price of the items I added to the cart. One catch
though, I don't have any concrete instances of an Item. I wasn't even planning on doing any of
that until tomorrow. I really want to just focus on the ShoppingCart class.
Never fear, this is why I decided to use Phake. I remember reading about how it will allow me to
quickly create instance of my classes and interfaces that I can set up stubs for so that method
calls return predictable values. This project is all coming together and I am really excited.
Example 2.2. ShoppingCartTest
<?php class ShoppingCartTest extends PHPUnit_Framework_TestCase { private $shoppingCart; private $item1; private $item2; private $item3; public function setUp() { $this->item1 = Phake::mock('Item'); $this->item2 = Phake::mock('Item'); $this->item3 = Phake::mock('Item'); Phake::when($this->item1)->getPrice()->thenReturn(100); Phake::when($this->item2)->getPrice()->thenReturn(200); Phake::when($this->item3)->getPrice()->thenReturn(300); $this->shoppingCart = new ShoppingCart(); $this->shoppingCart->addItem($this->item1); $this->shoppingCart->addItem($this->item2); $this->shoppingCart->addItem($this->item3); } public function testGetSub() { $this->assertEquals(600, $this->shoppingCart->getSubTotal()); } } ?>
My test here shows a very basic use of Phake for creating method stubs. I am creating three different mock
implementations of the Item class. Then for each of those item classes, I am creating
a stub using Phake::when() that will return 100, 200, and 300 respectively. I know my method
that I am getting ready to implement will need to call those methods in order to calculate the total cost of the
order.
My test is written so now it is time to see how it fails. I run it with phpunit and see the output in Example 2.3, “ShoppingCartTest Output”
Example 2.3. ShoppingCartTest Output
$ phpunit ExampleTests/ShoppingCartTest.php PHPUnit 3.5.13 by Sebastian Bergmann. F Time: 0 seconds, Memory: 8.50Mb There was 1 failure: 1) ShoppingCartTest::testGetSub Failed asserting that <null> matches expected <integer:600>. /home/mikel/Documents/Projects/Phake/tests/ShoppingCartTest.php:69 FAILURES! Tests: 1, Assertions: 1, Failures: 1. Generating code coverage report, this may take a moment.
Now that I have a working (and I by working I mean breaking!) test it is time to look at the code necessary to make the test pass.
Example 2.4. Cool Cart
<?php class ShoppingCart { // I am ]cutting out the already seen code. If you want to see it again look at the previous examples! /** * Returns the current sub total of the customer's order * @return money */ public function getSubTotal() { $total = 0; foreach ($this->items as $item) { $total += $item->getPrice(); } return $total; } } ?>
The code here is pretty simple. I am just iterating over the ShoppingCart::$item property,
calling the Item::getPrice() method, and adding them all together. Now when I run my test, I
get Example 2.5, “ShoppingCartTest Output”. The tests were successful and I am getting
off to a great start with my shopping cart.
Example 2.5. ShoppingCartTest Output
$ phpunit ExampleTests/ShoppingCartTest.php PHPUnit 3.5.13 by Sebastian Bergmann. . Time: 0 seconds, Memory: 8.25Mb OK (1 test, 1 assertion) Generating code coverage report, this may take a moment.
So, what is Phake doing here? Phake is providing us a predictable implementation of the Item::getPrice()
method that we can use in our test. It helps me to ensure the when my test breaks I know exactly where it is breaking.
I will not have to be worried that a bad implementation of Item::getPrice() is breaking my tests.