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. Testing Wizardry
    how mocks and fixtures can save your sanity
    Jeff Carouth #ZendCon 2012

    View Slide

  2. Jeff Carouth
    tweets jcarouth
    blogs http://carouth.com
    checks [email protected]
    works

    View Slide

  3. Writing Tests
    Is easy

    View Slide

  4. Is more difficult
    Writing Effective Tests

    View Slide

  5. Unit Tests Vs. Integration Tests

    View Slide

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

    View Slide

  7. Why Mocks?
    Objects which have states that are difficult to create
    or reproduce.

    View Slide

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

    View Slide

  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());
    }
    }

    View Slide

  10. Stubs (in PHPUnit) Can:
    returnValue(‘hello world!’)
    returnArgument(1)
    returnSelf()
    returnValueMap($map)
    returnCallback(‘str_rot13’)
    throwException(new OopsException())

    View Slide

  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!

    View Slide

  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();
    }
    }

    View Slide

  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
    );
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  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}"
    );
    }
    }
    }
    }

    View Slide

  16. Phake
    http://phake.digitalsandwich.com/docs/html/

    View Slide

  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();
    }
    }

    View Slide

  18. Mockery
    https://github.com/padraic/mockery

    View Slide

  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();
    }
    }

    View Slide

  20. Fixtures Defined

    View Slide

  21. DbUnit

    View Slide

  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");
    }
    }

    View Slide



  23. completed="false" />
    completed="false" />
    completed="false" />
    completed="false" />

    View Slide

  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.'
    );
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  26. Phabric
    https://github.com/benwaine/Phabric

    View Slide

  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);
    }
    }

    View Slide

  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);
    }

    View Slide

  29. Faker
    https://github.com/fzaninotto/Faker

    View Slide

  30. require_once __DIR__ . "/vendor/autoload.php";
    $faker = Faker\Factory::create();
    print '' . PHP_EOL;
    print '' . PHP_EOL;
    for ($i = 0; $i < 50; $i++) {
    print " firstName}\" " .
    "lastname=\"{$faker->lastName}\" " .
    "email=\"{$faker->email}\" />" . PHP_EOL;
    }
    print '';

    View Slide

  31. View Slide

  32. Thank You!
    https://joind.in/7039
    @jcarouth | [email protected]
    http://speakerdeck.com/jcarouth

    View Slide