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
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
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();
}
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
Stub Example
public function testGetUserInfo()
{
$userInfo = array(
'first_name' => 'Joe',
'last_name' => 'Strummer',
'id' => 1,
'email' => 'joe.strummer@gmail.com'
);
$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
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
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
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!
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();
}