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

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
  2. We’ll be covering ✤ Defining dependencies ✤ Dependency Injection ✤

    Test Doubles in Theory ✤ Test Doubles in Practice Saturday, March 15, 14
  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
  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
  5. 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
  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! Saturday, March 15, 14
  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
  8. 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
  9. Dependency Injection ✤ Helps make code testable ✤ Helps make

    code flexible ✤ Constructor/Accessor methods Saturday, March 15, 14
  10. MOAR Dependency Injection ✤ Dependencies become properties in the object

    in which they’re used ✤ Paramount for mocking in unit tests! Saturday, March 15, 14
  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 Saturday, March 15, 14
  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 Saturday, March 15, 14
  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 Saturday, March 15, 14
  14. Types of Test Doubles ✤ Mock ✤ Stub ✤ Dummy

    ✤ Spy Saturday, March 15, 14
  15. Mock ✤ Verifies that a method has been called correctly

    ✤ Doesn’t generate a response Saturday, March 15, 14
  16. Anatomy of a Mock ✤ Expectation ✤ Method ✤ Parameters

    (if applicable) Saturday, March 15, 14
  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(); } Saturday, March 15, 14
  18. Explanation ✤ Supposes $user->setUserId(1) will be called in the test

    ✤ Fails if $user->setUserId(1) is not called Saturday, March 15, 14
  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 :) Saturday, March 15, 14
  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 Saturday, March 15, 14
  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), ) } Saturday, March 15, 14
  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' ) } Saturday, March 15, 14
  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 Saturday, March 15, 14
  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)); ... Saturday, March 15, 14
  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 Saturday, March 15, 14
  26. Dummy ✤ It’s a place holder ✤ It has no

    expectations or behavior ✤ It satisfies a parameter list... Saturday, March 15, 14
  27. Dummy Example <?php class Comment { public function __construct($comment, User

    $user) { ... } public function validateComment() { //doesn't rely on User at all } } Saturday, March 15, 14
  28. Dummy Example public function testValidateComment() { $user = $this->getMock('User'); $commentText

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

    = "<script></script>"; $comment = new Comment($commentText,$user); $this->assertFalse($comment->validateComment()); } User fulfills the method signature, but doesn’t get used Saturday, March 15, 14
  30. Isolation! ✤ External Data Sources - don’t talk to em!

    ✤ APIs ✤ Database Responses Saturday, March 15, 14
  31. 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
  32. Constructor <?php class PDOTestHelper extends PDO { public function __construct()

    { } } Overridden constructor allows us to mock! Saturday, March 15, 14
  33. 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
  34. 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
  35. 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
  36. 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
  37. Returning Data ✤ Data Fixtures ✤ Data Providers ✤ Data

    should resemble what you expect to get back Saturday, March 15, 14
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. XPMock ✤ Sits on top of PHPUnit ✤ Less work

    to mock dependencies Saturday, March 15, 14
  45. Example - PHPUnit <?php $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
  46. 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
  47. Summary ✤ Injected Dependencies = Increased Testability ✤ Mock/Stub/Dummy ✤

    Don’t do more than you need to! ✤ Practice makes perfect Saturday, March 15, 14
  48. Victory! Mocking effectively leads to better tests and better tests

    lead to better applications! Saturday, March 15, 14