I use the Yii framework for one month and I find information of Unittest for the Yii framework at many places. But I don’t find an complete example of an unittest with datafiles in memory and mockup for Yii classes. So I write down my solution.
I want to read a datafile from my “FritzBox” that is in ascii format into my MySql database. For the import I need to read the file line by line and generate SQL-Insertstatements. In this example I don’t use ORM only DAO. The import function work correct but I want a unittest for the function. The unittest should only test the import and not the Yii functionality for DAO and I don’t want an real database management for the test. So I want to use mockups for the class CDbConnection and CDbCommand. The testdata should by in spezial fixture files (PHP-File) and not originaly datafiles from my “FritzBox”. I use for that ‘vfsStream’ for the test datafile.
My CFormModel looks like this:
class Rohdaten01importForm extends CFormModel
{
…
/**
* Import the content of the file into the database.
*
* @param string $date in the form dd.mm.yyyy
* @param string $filename that is expanded with _yyyymmdd.db
* @param CDbConnection $dbConnection the connection to the database
* @return number of rows that are inderted in the database.
*/
public function import($date, $filename, CDbConnection $dbConnection=null)
{
/**
* Insert Statement for the DAO. Later it should use the database
* attributes from the model
*/
$insertStatement = "INSERT INTO `tbl_rohdaten01` (`date`, `secofday`, `w2`, `resto2`, `aussentemp`, `vorlauf`, `boilerpump`, `boilertemp`) VALUES ";
// Convert the date in the form yyyymmdd and in the database form.
$date = Helper::GermanDate2Ymd($date);
$dbdate = substr($date, 0, 4) . '-' . substr($date, 4, 2) . '-' . substr($date, 6, 2);
// If we don't have assign database connectio, create it.
if (!isset($dbConnection))
{
$dbConnection = yii::app()->db;
}
// Construct the filename and extend _yyyymmdd.db
$filename = $filename . '_' . $date . '.db';
// Read the hole content of the file line by line
$fHandle = fopen($filename, 'r');
for($rownum=0; !feof($fHandle); $rownum++)
{
// Convert the row into columns
$line = fgets($fHandle);
$columns = explode(' ', rtrim($line));
// Create the SQL-Command with the connection.
$dbcommand = $dbConnection->createCommand($insertStatement . "( '$dbdate',$columns[0],$columns[1],$columns[2],$columns[3],$columns[4],$columns[5],$columns[6] )");
// Execute the Command
$dbcommand->execute();
}
// return the number of inserted rows.
return $rownum;
}
…
}
Here you can see the import function, that read the content line by line, split the row into an array and call for each line the insert SQL-Statement.
In my testfunction testImport I want an unittets, that don’t need a database an no physical datafile. So I write an setup routine to manage the testdata from my “fixture”.
require(dirname(__FILE__) . '/../fixtures/HeizDrahtRohDaten01.init.php');
require_once 'vfsStream/vfsStream.php';
class Rohdaten01importFormTest extends CDbTestCase
{
private $_form;
private $_mockCDbConnection;
private $_mockCDbCommand;
/**
* @var string the virtuell datafile from vfsStream
*/
protected $_testFilename = 'vfs://testDir/HeizDrahtRohdaten01';
/**
* setUp initialize the test environment.
*
* @see CDbTestCase::setUp()
*/
function setUp()
{
parent::setUp();
// Create the form
$this->_form = new Rohdaten01importForm();
// Create the content for the datafile
// 'vfs://testDir/HeizDrahtRohdaten01_yyyymmdd.db'
// from the "fixture" HeizDrahtRohDaten01.init.php
vfsStreamWrapper::register();
$root = vfsStream::setup('testDir');
$filename = $this->_testFilename . '_' . date('Ymd') . '.db';
// Create the datafile in memory with content
file_put_contents($filename, implode("\n",
HeizDrahtRohDaten01::getTestData()));
}
After the setup I call the import function with my mockups for CDbConnection and CDbCommand, so I don’t need a real database system. I only test that the Yii functions ‘createCommand’ and ‘execute’ are called from the import function. That the function ‘createCommand’ and ‘execute’ are correct implemented from the Yii framework is naturally .
This is the test function for the unittest (see inline comment).
/**
* The test function for Import
*/
public function testImport()
{
// Define the expected rows that should be inserted.
$expectedRows = HeizDrahtRohDaten01::getNumSets();
// Create a MockUp of CDbConnection. We overwrite only the command
// 'createCommand', because that
// is the routine we call in the import function.
$this->_mockCDbConnection = $this->getMock('CDbConnection',
array('createCommand'));
// Create a MockUp of CDbCommand. We overwrite only the command
// 'execute', because that is the routine we call in the import
// function.
// We don‘t want to execute the originaly contructor of the class
// of CdbCommand, so we need have to set the fifth parameter to false
$this->_mockCDbCommand = $this->getMock('CDbCommand', array('execute'),
array(), '', false);
// We need the date in dbDate format for the test
$dbdate=date('Y-m-d');
// For the first call we expected for the function call of
// 'createCommand':
// a string with the insert statement and the values of the first
// line from the fixture,
// we return the object of the mockCDbCommand, so the import function
// can call our Mock execute.
$this->_mockCDbConnection->expects($this->at(0))
->method('createCommand')
->with($this->equalTo("INSERT INTO `tbl_rohdaten01` (`date`, `secofday`, `w2`, `resto2`, `aussentemp`, `vorlauf`, `boilerpump`, `boilertemp`) VALUES ( '$dbdate',6,1,19,12,47,0,86 )"))
->will($this->returnValue($this->_mockCDbCommand));
// For the second call we expected for the function call of
// 'createCommand':
// a string with the insert statement and the values of the second
// line from the fixture,
// we return the object of the mockCDbCommand, so the import function
// can call our Mock execute.
$this->_mockCDbConnection->expects($this->at(1))
->method('createCommand')
->with($this->equalTo("INSERT INTO `tbl_rohdaten01` (`date`, `secofday`, `w2`, `resto2`, `aussentemp`, `vorlauf`, `boilerpump`, `boilertemp`) VALUES ( '$dbdate',66,2,19,20,30,0,87 )"))
->will($this->returnValue($this->_mockCDbCommand));
/*
* Here we can insert much more line for testing.
*/
// This line ist special. If we change the numbers of rows for testing
// in our fixture.
// The import function call 'createCommand' more than two times. If we
// don't have this line, the import function call 'createCommand' and
// have no return of the object 'CDbCommand'.
// The function call then null->execute() and we would have an error and
// not a good result of the unittest.
$this->_mockCDbConnection->expects($this->any())
->method('createCommand')
->will($this->returnValue($this->_mockCDbCommand));
// This assert test if the command 'execute' of out mockCDbCommand is
// call of the correct quantity.
$this->_mockCDbCommand->expects($this->exactly($expectedRows))
->method('execute');
// At this point we have the call of the function that we want to test.
// We call the function with the actual date
// and the datafile that is in memory
// and not the Yii original CDbConnection, but our MockUp Object.
$actrows = $this->_form->import(date('d.m.Y'), $this->_testFilename, $this->_mockCDbConnection);
// At the end of the unittest we test the number of rows that are
// inserted.
$this->assertEquals($expectedRows, $actrows, 'not correct lines in file');
}
}
To complete the example here is my "fixture" class for the testdata:
class HeizDrahtRohDaten01
{
static $_data = array(
'L1' => "6 1 19 12 47 0 86",
'L2' => "66 2 19 20 30 0 87",
// 'L3' => "45 8 10 20 34 1 87",
);
static function getTestData()
{
return self::$_data;
}
static function getTestSet($name)
{
return self::$_data[$name];
}
static function getNumSets()
{
return count(self::$_data);
}
}
I use eclipse PDT on Windows with XAMPP for this example.
Hope it helps someone!
//eRSTer2006