-
-
Save ikwattro/3555276 to your computer and use it in GitHub Desktop.
Small sample lesson on mocking database operations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MOCKING DATABASE OPERATIONS | |
=========================== | |
In our webreg example, we refactored some code so that we could | |
pass in our database object instead of creating it inside. Let's | |
push that further and show a slightly advanced usage of a mock | |
object. | |
So let's rework our Franchise test to use a mocked database connection. | |
Here's the code for the object: | |
class FranchiseModel | |
{ | |
protected $_db; | |
public function __construct($db) | |
{ | |
$this->_db = $db; | |
} | |
public function getByConference($conference) | |
{ | |
$sql = "SELECT nickname FROM franchises WHERE conference = '{$conference}' ORDER BY nickname"; | |
$result = $this->_db->query($sql); | |
$franchises = array(); | |
while ($result->fetchInto($row, DB_FETCHMODE_ASSOC)) { | |
$franchises[] = trim($row['nickname']); | |
} | |
return $franchises; | |
} | |
} | |
First step is to recognize that we have some code that cannot be tested. | |
In the source for the PEAR DB object we are using, fetchInto is using | |
call-by-reference as a lazy way to populate a resulting row from a | |
query into the $row. You cannot mock this since runtime call-by-referencing | |
throws errors in the latest stable versions of PHP. So, a quick refactor | |
is in order | |
<?php | |
class FranchiseModel | |
{ | |
protected $_db; | |
public function __construct($db) | |
{ | |
$this->_db = $db; | |
} | |
public function getByConference($conference) | |
{ | |
$sql = "SELECT nickname FROM franchises WHERE conference = '{$conference}' ORDER BY nickname"; | |
$result = $this->_db->query($sql); | |
$franchises = array(); | |
while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { | |
$franchises[] = trim($row['nickname']); | |
} | |
return $franchises; | |
} | |
} | |
Still functions the same, just uses a different method. So, with that | |
done we then have to switch to using mock objects. Here's the newly | |
refactored test | |
public function testGetNicknamesByConference() | |
{ | |
$fakeTeam = array('nickname' => 'FOO'); | |
$mockQuery = $this->getMockBuilder('stdClass') | |
->setMethods(array('fetchRow')) | |
->getMock(); | |
// Create a result set with 12 elements in it | |
$mockQuery->expects($this->any()) | |
->method('fetchRow') | |
->will($this->onConsecutiveCalls( | |
$fakeTeam, $fakeTeam, $fakeTeam, $fakeTeam, | |
$fakeTeam, $fakeTeam, $fakeTeam, $fakeTeam, | |
$fakeTeam, $fakeTeam, $fakeTeam, $fakeTeam | |
)); | |
$db = $this->getMockBuilder('stdClass') | |
->setMethods(array('query')) | |
->getMock(); | |
$db->expects($this->any()) | |
->method('query') | |
->will($this->returnValue($mockQuery)); | |
$testFranchiseModel = new FranchiseModel($db); | |
// 'AC' represents American Conference | |
$testFranchises = $testFranchiseModel->getByConference('AC'); | |
// We should have 12 franchises in this conference | |
$this->assertEquals( | |
12, | |
count($testFranchises), | |
'Did not get expected number of conferences' | |
); | |
} | |
Lots going on here, so I will explain. | |
First, I create one fake result row from the database. Then | |
I create a mock object off of PHP's built-in stdClass object | |
to act as a stand-in for what our $db object would return | |
when you execute a query. Because *what* type of object gets | |
returned isn't important, we can substitute stdClass. | |
I mock my fetchRow method and tell it "I'm expecting 12 calls | |
to be made and return these 12 things in the order I gave | |
them to you.". That covers what we are expecting our while | |
loop to be doing. | |
Having my result set object mocked, I then create a mock DB | |
object (again, the exact type didn't matter so I could just | |
use stdClass) and tell it I am going to be mocking the 'query' | |
method. | |
I then say "when someone calls query(), return this object | |
I have given you", where this object is the mocked query | |
object I created previously. | |
So now I have a test that is both passing AND no longer | |
uses the database at all in it. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment