Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Saturday, March 15, 14

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Mocking Saturday, March 15, 14

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Dummy Example

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Constructor

Slide 35

Slide 35 text

Constructor

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Example - PHPUnit 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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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