mocking dependencies in phpunit

Download Mocking Dependencies in PHPUnit

Post on 09-Jul-2015

355 views

Category:

Technology

3 download

Embed Size (px)

TRANSCRIPT

  • Mocking Dependencies in PHPUnitMatt Frost IRC: mfrost503 Feedback: http://joind.in/8693

  • Well be coveringDefining dependenciesDependency InjectionTest Doubles in TheoryTest Doubles in Practice

  • Whats a dependencyUnit Test ContextA unit(s) of code that adds functionality to another unit of codeThink system dependencies, but much smaller scale

  • Why mock them?Unit tests should cover a single unit of code in isolationA bug in a dependency makes your test a guessing gameWe only want to know that the code were testing works

  • Dependencies in the wildclass 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); }}

  • Dont do this!class Auth{ public function authenticate($username, $pass) { $user = new User($username, $pass); $username = $user->getUserName(); $password = $user->getHash(); $this->checkLogin($username,$password); }

  • Dependency InjectionHelps make code testableHelps make code flexibleConstructor/Accessor methods

  • MOAR Dependency InjectionDependencies become properties in the object in which theyre usedParamount for mocking in unit tests!

  • Mocking

  • Defining Test DoublesStand in for actual objects (think Stunt Doubles)Can simulate functionality from those objectsCan fulfill the requirements of a type hinted methodCan be used to make sure a method on the mock is called

  • A few more pointsCant directly mock private or protected methodsOnly mock what you need to testIn a pinch, Reflection API can help test private/protected

  • TheoryUnit Test shouldnt be dependent on external data source availabilityUnit Test vs. Integration TestHow do I know if my query is right?Youre testing code, not network availability

  • Types of Test DoublesMockStubDummySpy

  • MockVerifies that a method has been called correctlyDoesnt generate a response

  • Anatomy of a MockExpectationMethodParameters (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(); }

  • ExplanationSupposes $user->setUserId(1) will be called in the testFails 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, its a fictional example...so I wouldnt use the code :)

  • Test StubEnsures a method is a called correctlyGenerates a fake response Response allows for different cases to be tested

  • ResponseLiterally declaring what the response will beDoesnt 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' => 'joe.strummer@gmail.com' ); $user = $this->getMock('User', array('retrieve')); $user->expects($this->once()) ->method('retrieve') ->will($this->returnValue($userInfo)); ...

  • Stub Example Contd ... $post = new Post($user); $post->retrieve(10); $information = $post->getUserInfo(); $this->assertEquals('Joe',$information['first_name']); $this->assertEquals('Strummer',$information['last_name']); }Here were asserting that retrieve is called correctly by validating that we get back what we expect

  • DummyIts a place holderIt has no expectations or behaviorIt satisfies a parameter list...

  • Dummy Example
  • 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 doesnt get used

  • Practical Examples!External Data Sources - dont talk to em!APIsDatabase Responses

  • Stubbing PDOConstructor 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
  • Setup/TearDownpublic 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 were expecting this call to succeed, we need to return the statement again. Once we get the statement back, weve 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 DataData FixturesData ProvidersData should resemble what you expect to get back

  • Mocking API CallsWrap it up, not just for testing for your own sanity!Once its wrapped it can be mocked like anything elseSpies!Dont talk to the API

  • SpiesHelpful in making sure your method was calledOr called a certain number of timesNot commonly used, but Ive found good use in testing APIs

  • Practical API TestingGenerally, 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 typestringContains($value) - string parametercontains($value) - array parameterhasArrayKey($key)greaterThan($value)isInstanceOf($className)matchesRegularExpression($pattern)equalTo($value)

  • SummaryInjected Dependencies = Increased TestabilityMock/Stub/DummyDont 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: mfrost503Joind.in: http://joind.in/8693