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 full-size slide

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

    View full-size slide

  3. Writing Tests
    Is easy

    View full-size slide

  4. Is more difficult
    Writing Effective Tests

    View full-size slide

  5. Unit Tests Vs. Integration Tests

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size slide

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

    View full-size 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 full-size slide

  20. Fixtures Defined

    View full-size slide

  21. 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 full-size slide



  22. completed="false" />
    completed="false" />
    completed="false" />
    completed="false" />

    View full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

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

    View full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

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

    View full-size slide

  29. 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 full-size slide

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

    View full-size slide