Mocking Dependencies in PHPUnit
-
Upload
mfrost503 -
Category
Technology
-
view
369 -
download
3
Transcript of Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
Matt Frost · IRC: mfrost503 · Feedback: http://joind.in/8693
We’ll be covering
✤ Defining dependencies
✤ Dependency Injection
✤ Test Doubles in Theory
✤ Test Doubles in Practice
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
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
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!
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
Dependency Injection
✤ Helps make code testable
✤ Helps make code flexible
✤ Constructor/Accessor methods
MOAR Dependency Injection
✤ Dependencies become properties in the object in which they’re used
✤ Paramount for mocking in unit tests!
Mocking
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
A few more points
✤ Can’t directly mock private or protected methods
✤ Only mock what you need to test
✤ In a pinch, Reflection API can help test private/protected
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
Types of Test Doubles
✤ Mock
✤ Stub
✤ Dummy
✤ Spy
Mock
✤ Verifies that a method has been called correctly
✤ Doesn’t generate a response
Anatomy of a Mock
✤ Expectation
✤ Method
✤ Parameters (if applicable)
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(); }
Explanation
✤ Supposes $user->setUserId(1) will be called in the test
✤ Fails if $user->setUserId(1) is not called
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 :)
Test Stub
✤ Ensures a method is a called correctly
✤ Generates a “fake response”
✤ Response allows for different cases to be tested
Response
✤ Literally declaring what the response will be
✤ Doesn’t have to be a value, can throw Exceptions!
✤ Can show if your code is behaving/failing correctly
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)); ...
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
Dummy
✤ It’s a place holder
✤ It has no expectations or behavior
✤ It satisfies a parameter list...
Dummy Example
<?phpclass Comment{ public function __construct($comment, User $user) { ... } public function validateComment() { //doesn't rely on User at all }}
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
Practical Examples!
✤ External Data Sources - don’t talk to em!
✤ APIs
✤ Database Responses
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
Constructor
<?phpclass PDOTestHelper extends PDO{ public function __construct() { }}
Overridden constructor allows us to mock!
Setup/TearDown
public function setUp(){ $this->pdo = $this->getMock('PDOTestHelper'); $this->statement = $this->getMock('PDOStatement');}public function tearDown(){ unset($pdo); unset($statement);}
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.
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!
Stubbing Fetch!
$simData = array( ‘id‘ => 1, ‘firstName‘ => ‘Lloyd’, ‘lastName‘ => ‘Christmas’, ‘occupation‘ => ‘Dog Groomer’ ); $this->statement->expects($this->once()) ->method('fetch') ->will($this->returnValue($simData));
Returning Data
✤ Data Fixtures
✤ Data Providers
✤ Data should resemble what you expect to get back
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
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
Practical API Testing
✤ Generally, mocks suffice!
✤ If the method is transforming data, stub it!
✤ Spies are good to track multiple calls in same method
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(); }
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(); }
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)
Summary
✤ Injected Dependencies = Increased Testability
✤ Mock/Stub/Dummy
✤ Don’t do more than you need to!
✤ Practice makes perfect
Victory!
Mocking effectively leads to better tests and better tests lead to better applications!
Thank you!
✤ Freenode: mfrost503
✤ Joind.in: http://joind.in/8693