Skip to content

Instantly share code, notes, and snippets.

@ikwattro
Forked from chartjes/gist:3552838
Created August 31, 2012 16:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ikwattro/3555276 to your computer and use it in GitHub Desktop.
Save ikwattro/3555276 to your computer and use it in GitHub Desktop.
Small sample lesson on mocking database operations
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