Testing Wizardry: How Mocks and Fixtures Can Save Your Sanity

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.

0f930e13633535c1c4041e95b8881308?s=128

Jeff Carouth

October 25, 2012
Tweet

Transcript

  1. Testing Wizardry how mocks and fixtures can save your sanity

    Jeff Carouth #ZendCon 2012
  2. Jeff Carouth tweets jcarouth blogs http://carouth.com checks jcarouth@gmail.com works

  3. Writing Tests Is easy

  4. Is more difficult Writing Effective Tests

  5. Unit Tests Vs. Integration Tests

  6. Fake or Simulated object exposing the same interface as the

    object it mimics
  7. Why Mocks? Objects which have states that are difficult to

    create or reproduce.
  8. Mocks and Stubs with PHPUnit http://crth.net/phpunit-mock-docs

  9. 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()); } }
  10. Stubs (in PHPUnit) Can: returnValue(‘hello world!’) returnArgument(1) returnSelf() returnValueMap($map) returnCallback(‘str_rot13’)

    throwException(new OopsException())
  11. 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!
  12. 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(); } }
  13. 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 ); } }
  14. 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(); } }
  15. 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}" ); } } } }
  16. Phake http://phake.digitalsandwich.com/docs/html/

  17. 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(); } }
  18. Mockery https://github.com/padraic/mockery

  19. 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(); } }
  20. Fixtures Defined

  21. DbUnit

  22. 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"); } }
  23. <?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>
  24. 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.' ); } }
  25. 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(); } }
  26. Phabric https://github.com/benwaine/Phabric

  27. 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); } }
  28. 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); }
  29. Faker https://github.com/fzaninotto/Faker

  30. <?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>';
  31. <?xml version="1.0" ?> <dataset> <user firstname="Baby" lastname="Jones" email="vvon@yahoo.com" /> <user

    firstname="Juvenal" lastname="Prosacco" email="qmetz@veum.org" /> <user firstname="Wanda" lastname="Jewess" email="hartmann.aliyah@langworthokuneva.com" /> <user firstname="Hope" lastname="Rippin" email="leffler.elena@gmail.com" /> <user firstname="Zack" lastname="Hegmann" email="rwaelchi@hotmail.com" /> <user firstname="Lesley" lastname="Romaguera" email="rconroy@gmail.com" /> <user firstname="Kay" lastname="Sauer" email="maximus.grady@hotmail.com" /> <!-- snip --> </dataset>
  32. Thank You! https://joind.in/7039 @jcarouth | jcarouth@gmail.com http://speakerdeck.com/jcarouth