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