Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing Wizardry: How Mocks and Fixtures Can Save Your Sanity

Jeff Carouth
October 25, 2012

Testing Wizardry: How Mocks and Fixtures Can Save Your Sanity

Writing tests is easy. Writing tests that are thorough is hard. How do you test in isolation? It is a tough question when you start your testing journey, but with mocks and fixtures you too will be writing impressive and useful tests. In this talk we will explore native PHPUnit techniques for creating mocks and stubs, Phake and Mockery for enhanced mocking in unit tests, Phactory for fixture generation in unit tests, and Phabric for feature tests with Behat. At the end of this talk you will have gained knowledge of how and when to apply mocks and fixtures, and will be on your way to being a testing phreak.

Jeff Carouth

October 25, 2012
Tweet

More Decks by Jeff Carouth

Other Decks in Programming

Transcript

  1. interface MyInterface { public function sayHi(); } class FirstMockTest extends

    PHPUnit_Framework_TestCase { public function testJeffShouldBeAbleToMockHisInterface() { $mockMyInterface = $this->getMock('MyInterface'); $mockMyInterface->expects($this->any()) ->method('sayHi') ->will($this->returnValue('Oh, hi!')); $this->assertEquals('Oh, hi!', $mockMyInterface->sayHi()); } }
  2. class OrderTest extends PHPUnit_Framework_TestCase { public function testSaveOrderSendsEmailWithOrderConfirmation() { $order

    = new Order(new \EmailService\Service\Sendmail()); $order->setId(12345); $order->save(); // log into email account // find email in inbox // verify it has the correct subject // No. No. No. } } NO!
  3. class OrderTest extends PHPUnit_Framework_TestCase { public function testSaveOrderSendsEmailWithOrderConfirmation() { $mockEmailService

    = $this->getMock( '\\EmailService\\EmailServiceInterface' ); $mockEmailService->expects($this->once()) ->method('send'); $order = new Order($mockEmailService); $order->setId(12345); $order->save(); } }
  4. class Order { private $emailService; private $logger; private $id; public

    function __construct($service, $logger = null) { $this->emailService = $service; $this->logger = $logger; } public function save() { //do some saving logic $this->emailService->send( 'Order Confirmation #' . $this->id ); } }
  5. class OrderTest extends PHPUnit_Framework_TestCase { //...snip... public function testEmailConfirmationFailLogs() {

    $mockLogger = $this->getMock('\\System\\LoggerInterface'); $mockLogger->expects($this->once()) ->method('log'); $mockEmailService = $this->getMock( '\\EmailService\\EmailServiceInterface' ); $mockEmailService->expects($this->once()) ->method('send') ->will($this->throwException(new Exception())); $order = new Order($mockEmailService, $mockLogger); $order->setId(12345); $order->save(); } }
  6. class Order { public function save() { //do some saving

    logic try { $this->emailService->send( 'Order Confirmation #' . $this->id ); } catch (Exception $e) { if (null !== $this->logger) { $this->logger->log( "email failed for order {$this->id}" ); } } } }
  7. class OrderTest extends PHPUnit_Framework_TestCase { public function testSaveOrderSendsEmailWithOrderConfirmation() { $mockEmailService

    = Phake::mock( '\\EmailService\\EmailServiceInterface' ); Phake::when($mockEmailService)->send()->thenReturn(true); $order = new Order($mockEmailService); $order->setId(12345); $order->save(); Phake::verify($mockEmailService)->send(); } }
  8. class OrderTest extends PHPUnit_Framework_TestCase { public function tearDown() { m::close();

    } public function testSaveOrderSendsEmailWithOrderConfirmation() { $mockEmailService = \Mockery::mock( '\\EmailService\\EmailServiceInterface' ); $mockEmailService->shouldReceive('send') ->withAnyArgs() ->andReturn(true); $order = new Order($mockEmailService); $order->setId(12345); $order->save(); } }
  9. class TodoListTest extends PHPUnit_Extensions_Database_TestCase { private $pdo; /** * @return

    PHPUnit_Extensions_Database_DB_IDatabaseConnection */ public function getConnection() { $this->pdo = new PDO('DSN', 'USER', 'PASS'); return $this->createDefaultDBConnection( $this->pdo, 'todolist'); } /** * @return PHPUnit_Extensions_Database_DataSet_IDataSet */ public function getDataSet() { return $this->createFlatXMLDataSet( __DIR__ . "/fixtures/todo-seed.xml"); } }
  10. <?xml version="1.0" ?> <dataset> <todo id="1" summary="task 1" createdDate="2010-04-24 17:15:23"

    completed="false" /> <todo id="2" summary="task 2" createdDate="2010-04-24 17:15:23" completed="false" /> <todo id="3" summary="task 3" createdDate="2010-04-24 17:15:23" completed="false" /> <todo id="4" summary="task 4" createdDate="2010-04-24 17:15:23" completed="false" /> </dataset>
  11. class TodoListTest extends PHPUnit_Extensions_Database_TestCase { public function testGetCountReturnNumberOfUnfinishedTasks() { $todoModel

    = new TodoModel($this->pdo); $expectedNumberOfTodos = 4; $todos = $todoModel->getUnfinishedTasks(); $this->assertCount( $expectedNumberOfTodos, $todos, 'The expected number of unfinished tasks is wrong.' ); } }
  12. class TodoModel { private $db; public function __construct($db) { $this->db

    = $db; } public function getUnfinishedTasks() { $sql = "SELECT * FROM todo"; $result = $this->db->query($sql); return $result->fetchAll(); } }
  13. class FeatureContext extends MinkContext { protected $phabric; public function __construct(array

    $parameters) { $config = new \Doctrine\DBAL\Configuration(); self::$db = \Doctrine\DBAL\DriverManager::getConnection(...); $datasource = new \Phabric\Datasource\Doctrine( self::$db, $parameters['Phabric']['entities'] ); $this->phabric = new Phabric\Phabric($datasource); } }
  14. Feature: As an organized person I want to be able

    to manage my todo list So that I can ensure I accomplish my tasks Scenario: I want to see all incomplete tasks Given The following tasks exist | summary | createdDate | completed | estimate | | Task 1 | 2012-09-21 19:53:33 | false | 1 | | Task 2 | 2012-09-21 19:53:33 | false | 1 | | Task 3 | 2012-09-21 19:53:33 | false | 1 | When I am on "/tasks" Then I should see "3" tasks /** * @Given /^The following tasks exist$/ */ public function theFollowingTaskssExist(TableNode $table) { $this->phabric->insertFromTable('tasks', $table); }
  15. <?php require_once __DIR__ . "/vendor/autoload.php"; $faker = Faker\Factory::create(); print '<?xml

    version="1.0" ?>' . PHP_EOL; print '<dataset>' . PHP_EOL; for ($i = 0; $i < 50; $i++) { print " <user firstname=\"{$faker->firstName}\" " . "lastname=\"{$faker->lastName}\" " . "email=\"{$faker->email}\" />" . PHP_EOL; } print '</dataset>';
  16. <?xml version="1.0" ?> <dataset> <user firstname="Baby" lastname="Jones" email="[email protected]" /> <user

    firstname="Juvenal" lastname="Prosacco" email="[email protected]" /> <user firstname="Wanda" lastname="Jewess" email="[email protected]" /> <user firstname="Hope" lastname="Rippin" email="[email protected]" /> <user firstname="Zack" lastname="Hegmann" email="[email protected]" /> <user firstname="Lesley" lastname="Romaguera" email="[email protected]" /> <user firstname="Kay" lastname="Sauer" email="[email protected]" /> <!-- snip --> </dataset>