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

Mocking Dependencies in PHPUnit

Frost
May 23, 2014

Mocking Dependencies in PHPUnit

Mocking Dependencies in PHPUnit slides from PHP[tek] 2014

Frost

May 23, 2014
Tweet

More Decks by Frost

Other Decks in Technology

Transcript

  1. Mocking Dependencies in
    PHPUnit
    Matt Frost · IRC: mfrost503 · Feedback: http://joind.in/10643 · @shrtwhitebldguy!

    View Slide

  2. We’ll be covering
    ✤ Defining dependencies!
    ✤ Dependency Injection!
    ✤ Test Doubles in Theory!
    ✤ Test Doubles in Practice

    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

    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

    View Slide

  5. 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);!
    }!
    }
    Dependency
    Alert!

    View Slide

  7. 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

    View Slide

  8. Dependency Injection
    ✤ Helps make code testable!
    ✤ Helps make code flexible!
    ✤ Constructor/Accessor methods

    View Slide

  9. MOAR Dependency Injection
    ✤ Dependencies become properties in the object in which they’re used!
    ✤ Paramount for mocking in unit tests!

    View Slide

  10. Mocking

    View Slide

  11. 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

    View Slide

  12. 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

    View Slide

  13. 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

    View Slide

  14. Types of Test Doubles
    ✤ Mock!
    ✤ Stub!
    ✤ Dummy!
    ✤ Spy

    View Slide

  15. Mock
    ✤ Verifies that a method has been called correctly!
    ✤ Doesn’t generate a response

    View Slide

  16. Anatomy of a Mock
    ✤ Expectation!
    ✤ Method!
    ✤ Parameters (if applicable)

    View Slide

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

    View Slide

  18. Explanation
    ✤ Supposes $user->setUserId(1) will be called in the test!
    ✤ Fails if $user->setUserId(1) is not called

    View Slide

  19. 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 :)

    View Slide

  20. 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

    View Slide

  21. Data Providers
    public function NumberProvider()
    {
    array(
    array(2, 4),
    array(9, 81),
    array(4, 16),
    array(12, 144),
    array(13, 169),
    array(5, 25),
    )
    }

    View Slide

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

    View Slide

  23. 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!
    !
    !
    !
    !

    View Slide

  24. 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));
    ...

    View Slide

  25. 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

    View Slide

  26. Dummy
    ✤ It’s a place holder!
    ✤ It has no expectations or behavior!
    ✤ It satisfies a parameter list...

    View Slide

  27. Dummy Example
    class Comment
    {
    public function __construct($comment, User $user)
    {
    ...
    }
    public function validateComment()
    {
    //doesn't rely on User at all
    }
    }
    !

    View Slide

  28. 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

    View Slide

  29. Isolation!
    ✤ External Data Sources - don’t talk to em!!
    ✤ APIs!
    ✤ Database Responses

    View Slide

  30. 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

    View Slide

  31. Constructor
    class PDOTestHelper extends PDO
    {
    public function __construct()
    {
    }
    } Overridden constructor
    allows us to mock!

    View Slide

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

    View Slide

  33. 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.

    View Slide

  34. 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!

    View Slide

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

    View Slide

  36. Returning Data
    ✤ Data Fixtures!
    ✤ Data Providers!
    ✤ Data should resemble what you expect to get back

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  42. 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)

    View Slide

  43. Other options
    ✤ XPMock!
    ✤ Mockery!
    ✤ Phake

    View Slide

  44. XPMock
    ✤ Sits on top of PHPUnit!
    ✤ Less work to mock dependencies

    View Slide

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

    View Slide

  46. Example XPMock
    $this->mock('MyClass')
    ->getBool(true)
    ->getNumber(1)
    ->getString('string')
    ->new();

    View Slide

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

    View Slide

  48. 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);

    View Slide

  49. Summary
    ✤ Injected Dependencies = Increased Testability!
    ✤ Mock/Stub/Dummy!
    ✤ Don’t do more than you need to!!
    ✤ Practice makes perfect

    View Slide

  50. Victory!
    Mocking effectively leads to better tests and better tests lead to
    better applications!

    View Slide

  51. Thank you!
    ✤ Freenode: mfrost503!
    ✤ Joind.in: http://joind.in/10643!
    ✤ Twitter: @shrtwhitebldguy

    View Slide