When testing code which uses the database, you would usually want to make sure the database stays pristine for each test – All tables should be empty, as any extra data could interfere with the tests.
You could probably write an extended Testcase class which automatically does this before each test in setUp and afterwards in tearDown, but it may have some issues. For example, Zend Framework’s Zend_Test Testcase class also extends PHPUnit’s one, and you might need to extend the Zend_Test one as well… and so on.
Luckily, PHPUnit has a way to add event listener that react to certain events.
TestListener
We can use PHPUnit’s TestListener interface to create an event handler class which runs before and after each test, resetting the database.
Since I’m using Doctrine, resetting the database is a very simple task: Just open a new in-memory SQLite database, and let Doctrine generate tables from my models. Since this new DB is in the memory, closing the connection will wipe it out. You can use the in-memory SQLite connection with other DB libraries or just PDO, but re-creating tables will be more troublesome.
But how to determine if a specific testcase even needs to use the database? You probably wouldn’t want to waste resources on recreating the database if it isn’t needed.
We could do it by adding a variable to our testcase, say public $usesDatabase = true;
, which the listener would then look at… but in a way, I didn’t like that… It doesn’t seem like good OOP to have a public variable like that in some of the classes and not in some others.
There is a nice alternative, though: Interfaces. If we make our testcase classes implement some specific interface, we could have the listener just check for that.
A sample implementation
Here’s an example implementation that I use with Doctrine:
interface DbTest_Interface { } class DbResetListener implements PHPUnit_Framework_TestListener { public function startTest(PHPUnit_Framework_Test $test) { if($test instanceof DbTest_Interface) { Doctrine_Manager::connection(new PDO('sqlite::memory:')); Doctrine::createTablesFromModels(); } } public function endTest(PHPUnit_Framework_Test $test, $time) { if($test instanceof DbTest_Interface) { Doctrine_Manager::getInstance()->closeConnection(Doctrine_Manager::connection()); } } //Other methods needed for interface but nothing else public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { } public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { } public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { } public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } } |
It’s quite simple. The listener just creates a new connection before each test and creates the model tables, and closes the connection after the test – but only if the testcase implements DbTest_Interface.
To use the above, you would first need to load your models using Doctrine::loadModels('/path/to/models');
, and adding the listener to the test, for example by doing..
class AllTests { public static function main() { $listener = new DbResetListener(); PHPUnit_TextUI_TestRunner::run(self::suite(), array( 'listeners' => array($listener) )); } /* other stuff here */ } |