Mocking Dependencies in
PHPUnit
Matt Frost · IRC: mfrost503 · Feedback: http://joind.in/10643 · @shrtwhitebldguy!
Slide 2
Slide 2 text
We’ll be covering
✤ Defining dependencies!
✤ Dependency Injection!
✤ Test Doubles in Theory!
✤ Test Doubles in Practice
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
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
Slide 5
Slide 5 text
No content
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);!
}!
}
Dependency
Alert!
Slide 7
Slide 7 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
Slide 8
Slide 8 text
Dependency Injection
✤ Helps make code testable!
✤ Helps make code flexible!
✤ Constructor/Accessor methods
Slide 9
Slide 9 text
MOAR Dependency Injection
✤ Dependencies become properties in the object in which they’re used!
✤ Paramount for mocking in unit tests!
Slide 10
Slide 10 text
Mocking
Slide 11
Slide 11 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
Slide 12
Slide 12 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
Slide 13
Slide 13 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
Slide 14
Slide 14 text
Types of Test Doubles
✤ Mock!
✤ Stub!
✤ Dummy!
✤ Spy
Slide 15
Slide 15 text
Mock
✤ Verifies that a method has been called correctly!
✤ Doesn’t generate a response
Slide 16
Slide 16 text
Anatomy of a Mock
✤ Expectation!
✤ Method!
✤ Parameters (if applicable)
Slide 17
Slide 17 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();
}
Slide 18
Slide 18 text
Explanation
✤ Supposes $user->setUserId(1) will be called in the test!
✤ Fails if $user->setUserId(1) is not called
Slide 19
Slide 19 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 :)
Slide 20
Slide 20 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
Slide 21
Slide 21 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),
)
}
Slide 22
Slide 22 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'
)
}
Slide 23
Slide 23 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!
!
!
!
!
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
Slide 26
Slide 26 text
Dummy
✤ It’s a place holder!
✤ It has no expectations or behavior!
✤ It satisfies a parameter list...
Slide 27
Slide 27 text
Dummy Example
Slide 28
Slide 28 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
Slide 29
Slide 29 text
Isolation!
✤ External Data Sources - don’t talk to em!!
✤ APIs!
✤ Database Responses
Slide 30
Slide 30 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
Slide 31
Slide 31 text
Constructor
Slide 32
Slide 32 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);
}
Slide 33
Slide 33 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.
Slide 34
Slide 34 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!
Returning Data
✤ Data Fixtures!
✤ Data Providers!
✤ Data should resemble what you expect to get back
Slide 37
Slide 37 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
Slide 38
Slide 38 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
Slide 39
Slide 39 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
Slide 40
Slide 40 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();
}
Slide 41
Slide 41 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();
}