$30 off During Our Annual Pro Sale. View Details »

Mocking Dependencies in PHPUnit

Frost
March 15, 2014

Mocking Dependencies in PHPUnit

Slides for talk that was given at Midwest PHP in Minneapolis, MN March 15, 2014

Frost

March 15, 2014
Tweet

More Decks by Frost

Other Decks in Technology

Transcript

  1. Mocking Dependencies in
    PHPUnit
    Matt Frost · IRC: mfrost503 · Feedback: http://joind.in/10547 · @shrtwhitebldguy
    Saturday, March 15, 14

    View Slide

  2. We’ll be covering
    ✤ Defining dependencies
    ✤ Dependency Injection
    ✤ Test Doubles in Theory
    ✤ Test Doubles in Practice
    Saturday, March 15, 14

    View Slide

  3. What’s a dependency
    ✤ Unit Test Context
    ✤ A unit(s) of code that adds functionality to another unit of code
    ✤ Think system dependencies, but much smaller scale
    Saturday, March 15, 14

    View Slide

  4. Why mock them?
    ✤ Unit tests should cover a single unit of code in isolation
    ✤ A bug in a dependency makes your test a guessing game
    ✤ We only want to know that the code we’re testing works
    Saturday, March 15, 14

    View Slide

  5. Saturday, March 15, 14

    View Slide

  6. Dependencies in the wild
    class Auth
    {
    private $user;
    public function __construct(User $user)
    {
    $this->user = $user;
    }
    public function authenticate()
    {
    $username = $this->user->getUserName();
    $password = $this->user->getHash();
    $this->checkLogin($username,$password);
    }
    }
    Saturday, March 15, 14

    View Slide

  7. Dependencies in the wild
    class Auth
    {
    private $user;
    public function __construct(User $user)
    {
    $this->user = $user;
    }
    public function authenticate()
    {
    $username = $this->user->getUserName();
    $password = $this->user->getHash();
    $this->checkLogin($username,$password);
    }
    }
    Dependency
    Alert!
    Saturday, March 15, 14

    View Slide

  8. Dependencies in the wild
    class Auth
    {
    private $user;
    public function __construct(User $user)
    {
    $this->user = $user;
    }
    public function authenticate()
    {
    $username = $this->user->getUserName();
    $password = $this->user->getHash();
    $this->checkLogin($username,$password);
    }
    }
    Dependency
    Alert!
    Saturday, March 15, 14

    View Slide

  9. Don’t do this!
    class Auth
    {
    public function authenticate($username, $pass)
    {
    $user = new User($username, $pass);
    $username = $user->getUserName();
    $password = $user->getHash();
    $this->checkLogin($username,$password);
    }
    User cannot
    be mocked
    Saturday, March 15, 14

    View Slide

  10. Dependency Injection
    ✤ Helps make code testable
    ✤ Helps make code flexible
    ✤ Constructor/Accessor methods
    Saturday, March 15, 14

    View Slide

  11. MOAR Dependency Injection
    ✤ Dependencies become properties in the object in which they’re used
    ✤ Paramount for mocking in unit tests!
    Saturday, March 15, 14

    View Slide

  12. Mocking
    Saturday, March 15, 14

    View Slide

  13. Defining Test Doubles
    ✤ Stand in for actual objects (think Stunt Doubles)
    ✤ Can simulate functionality from those objects
    ✤ Can fulfill the requirements of a type hinted method
    ✤ Can be used to make sure a method on the mock is called
    Saturday, March 15, 14

    View Slide

  14. A few more points
    ✤ Can’t directly mock private methods
    ✤ Only mock what you need to test
    ✤ In a pinch, Reflection API can help test private
    Saturday, March 15, 14

    View Slide

  15. Theory
    ✤ Unit Test shouldn’t be dependent on external data source availability
    ✤ Unit Test vs. Integration Test
    ✤ “How do I know if my query is right?”
    ✤ You’re testing code, not network availability
    Saturday, March 15, 14

    View Slide

  16. Types of Test Doubles
    ✤ Mock
    ✤ Stub
    ✤ Dummy
    ✤ Spy
    Saturday, March 15, 14

    View Slide

  17. Mock
    ✤ Verifies that a method has been called correctly
    ✤ Doesn’t generate a response
    Saturday, March 15, 14

    View Slide

  18. Anatomy of a Mock
    ✤ Expectation
    ✤ Method
    ✤ Parameters (if applicable)
    Saturday, March 15, 14

    View Slide

  19. Mock Example
    public function testSetUser()
    {
    $user = $this->getMock('User',array('setUserId'));
    $user->expects($this->once())
    ->method('setUserId')
    ->with(1);
    $post = new Post($user);
    $post->retrieve(10);
    $post->getUserInfo();
    }
    Saturday, March 15, 14

    View Slide

  20. Explanation
    ✤ Supposes $user->setUserId(1) will be called in the test
    ✤ Fails if $user->setUserId(1) is not called
    Saturday, March 15, 14

    View Slide

  21. Mock Implementation
    public function getUserInfo()
    {
    // assume $this->data is populated from
    // the $post->retrieve($id) method
    $userId = $this->data['user_id'];
    $this->user->setUserId($userId);
    return $this->user->retrieve();
    }
    This is an example of code that would pass the previous test,
    it’s a fictional example...so I wouldn’t use the code :)
    Saturday, March 15, 14

    View Slide

  22. Test Stub
    ✤ Ensures a method is a called correctly
    ✤ Generates a “fake response”
    ✤ Response allows for different cases to be tested
    ✤ Data Fixtures/Data Providers
    Saturday, March 15, 14

    View Slide

  23. Data Providers
    public function NumberProvider()
    {
    array(
    array(2, 4),
    array(9, 81),
    array(4, 16),
    array(12, 144),
    array(13, 169),
    array(5, 25),
    )
    }
    Saturday, March 15, 14

    View Slide

  24. Using a data provider
    /**
    * @dataProvider NumberProvider
    */
    public function testSquares($integer, $value)
    {
    $mathOperations = new MathOperations();
    $square = $mathOperations->square($integer);
    $this->assertEquals(
    $value,
    $square,
    'Square value did not match expectation'
    )
    }
    Saturday, March 15, 14

    View Slide

  25. Response
    ✤ Declare a response for a particular case
    ✤ Doesn’t have to be a value, can throw Exceptions!
    ✤ Can show if your code is behaving/failing correctly
    Saturday, March 15, 14

    View Slide

  26. Stub Example
    public function testGetUserInfo()
    {
    $userInfo = array(
    'first_name' => 'Joe',
    'last_name' => 'Strummer',
    'id' => 1,
    'email' => '[email protected]'
    );
    $user = $this->getMock('User', array('retrieve'));
    $user->expects($this->once())
    ->method('retrieve')
    ->will($this->returnValue($userInfo));
    ...
    Saturday, March 15, 14

    View Slide

  27. Stub Example Cont’d
    ...
    $post = new Post($user);
    $post->retrieve(10);
    $information = $post->getUserInfo();
    $this->assertEquals('Joe',$information['first_name']);
    $this->assertEquals('Strummer',$information['last_name']);
    }
    Here we’re asserting that retrieve is called correctly by validating
    that we get back what we expect
    Saturday, March 15, 14

    View Slide

  28. Dummy
    ✤ It’s a place holder
    ✤ It has no expectations or behavior
    ✤ It satisfies a parameter list...
    Saturday, March 15, 14

    View Slide

  29. Dummy Example
    class Comment
    {
    public function __construct($comment, User $user)
    {
    ...
    }
    public function validateComment()
    {
    //doesn't rely on User at all
    }
    }
    Saturday, March 15, 14

    View Slide

  30. Dummy Example
    public function testValidateComment()
    {
    $user = $this->getMock('User');
    $commentText = "";
    $comment = new Comment($commentText,$user);
    $this->assertFalse($comment->validateComment());
    }
    Saturday, March 15, 14

    View Slide

  31. Dummy Example
    public function testValidateComment()
    {
    $user = $this->getMock('User');
    $commentText = "";
    $comment = new Comment($commentText,$user);
    $this->assertFalse($comment->validateComment());
    }
    User fulfills the method signature, but doesn’t get used
    Saturday, March 15, 14

    View Slide

  32. Isolation!
    ✤ External Data Sources - don’t talk to em!
    ✤ APIs
    ✤ Database Responses
    Saturday, March 15, 14

    View Slide

  33. Stubbing PDO
    ✤ Constructor is not serializable, we must adapt!
    ✤ PDO::prepare - returns a PDO Statement (which we can stub)
    ✤ We can easily cover a variety of outcomes
    Saturday, March 15, 14

    View Slide

  34. Constructor
    class PDOTestHelper extends PDO
    {
    public function __construct()
    {
    }
    }
    Saturday, March 15, 14

    View Slide

  35. Constructor
    class PDOTestHelper extends PDO
    {
    public function __construct()
    {
    }
    } Overridden constructor
    allows us to mock!
    Saturday, March 15, 14

    View Slide

  36. Setup/TearDown
    public function setUp()
    {
    $this->pdo = $this->getMock('PDOTestHelper', array(‘prepare’);
    $this->statement = $this->getMock('PDOStatement', array(‘execute’));
    }
    public function tearDown()
    {
    unset($pdo);
    unset($statement);
    }
    Saturday, March 15, 14

    View Slide

  37. Stubbing a prepared statement
    $this->pdo->expects($this->once())
    ->method('prepare')
    ->with($this->stringContains('SELECT * from table'))
    ->will($this->returnValue($this->statement))
    Prepare will return a PDOStatement when executed successfully, so in
    order to stub the preparation and execution of the query, this is how we
    need to start.
    Saturday, March 15, 14

    View Slide

  38. Stubbing the execute call
    $this->statement->expects($this->once())
    ->method('execute')
    ->with($this->isType('array'))
    ->will($this->returnValue($this->statement));
    Since we’re expecting this call to succeed, we need to return the
    statement again. Once we get the statement back, we’ve successfully
    simulated the preparation and execution of a query!
    Saturday, March 15, 14

    View Slide

  39. Stubbing Fetch!
    $simData = array(
    ‘id‘ => 1,
    ‘firstName‘ => ‘Lloyd’,
    ‘lastName‘ => ‘Christmas’,
    ‘occupation‘ => ‘Dog Groomer’
    );
    $this->statement->expects($this->once())
    ->method('fetch')
    ->will($this->returnValue($simData));
    Saturday, March 15, 14

    View Slide

  40. Returning Data
    ✤ Data Fixtures
    ✤ Data Providers
    ✤ Data should resemble what you expect to get back
    Saturday, March 15, 14

    View Slide

  41. Mocking API Calls
    ✤ Wrap it up, not just for testing for your own sanity!
    ✤ Once it’s wrapped it can be mocked like anything else
    ✤ Spies!
    ✤ Don’t talk to the API
    Saturday, March 15, 14

    View Slide

  42. Spies
    ✤ Helpful in making sure your method was called
    ✤ Or called a certain number of times
    ✤ Not commonly used, but I’ve found good use in testing APIs
    Saturday, March 15, 14

    View Slide

  43. Practical API Testing
    ✤ Generally, mocks suffice!
    ✤ If the method is transforming data, stub it!
    ✤ Spies are good to track multiple calls in same method
    Saturday, March 15, 14

    View Slide

  44. API Example
    public function testGetTweets()
    {
    //mock example
    $request = $this->getMock('Request',array('get'));
    $request->expects($this->once())
    ->method('get')
    ->with('statuses');
    $twitter = new Twitter($request);
    $twitter->getTweets();
    }
    Saturday, March 15, 14

    View Slide

  45. Spy Example
    public function testComplicatedMethod()
    {
    //spy example
    $request = $this->getMock('Request',array('get'));
    $request->expects($this->exactly(3))
    ->method('get');
    $twitter = new Twitter($request);
    $twitter->complicatedMethod();
    }
    Saturday, March 15, 14

    View Slide

  46. Helpful Tidbits - With()
    ✤ isType(String $type) - check by type
    ✤ stringContains($value) - string parameter
    ✤ contains($value) - array parameter
    ✤ hasArrayKey($key)
    ✤ greaterThan($value)
    ✤ isInstanceOf($className)
    ✤ matchesRegularExpression($pattern)
    ✤ equalTo($value)
    Saturday, March 15, 14

    View Slide

  47. Other options
    ✤ XPMock
    ✤ Mockery
    ✤ Phake
    Saturday, March 15, 14

    View Slide

  48. XPMock
    ✤ Sits on top of PHPUnit
    ✤ Less work to mock dependencies
    Saturday, March 15, 14

    View Slide

  49. Example - PHPUnit
    $mock = $this->getMockBuilder('MyClass')
    ->setMethods(['getBool', 'getNumber', 'getString'])
    ->disableOriginalConstructor()
    ->getMock();
    $mock->expects($this->any())
    ->method('getBool')
    ->will($this->returnValue(true));
    $mock->expects($this->any())
    ->method('getNumber')
    ->will($this->returnValue(1));
    $mock->expects($this->any())
    ->method('getString')
    ->will($this->returnValue('string'));
    Saturday, March 15, 14

    View Slide

  50. Example XPMock
    $this->mock('MyClass')
    ->getBool(true)
    ->getNumber(1)
    ->getString('string')
    ->new();
    Saturday, March 15, 14

    View Slide

  51. Mockery Example
    $service = m::mock('service');
    $service->shouldReceive('readTemp')->times(3)->andReturn(10, 12,
    14);
    Saturday, March 15, 14

    View Slide

  52. Phake Example
    $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);
    Saturday, March 15, 14

    View Slide

  53. Summary
    ✤ Injected Dependencies = Increased Testability
    ✤ Mock/Stub/Dummy
    ✤ Don’t do more than you need to!
    ✤ Practice makes perfect
    Saturday, March 15, 14

    View Slide

  54. Victory!
    Mocking effectively leads to better tests and better tests lead to
    better applications!
    Saturday, March 15, 14

    View Slide

  55. Thank you!
    ✤ Freenode: mfrost503
    ✤ Joind.in: http://joind.in/10547
    ✤ Twitter: @shrtwhitebldguy
    Saturday, March 15, 14

    View Slide