Database helper for PHPUnit

August 27, 2008 – 1:10 pm Tags: ,

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 */
}
Share this:

RSS feed Subscribe to my RSS feed

About the author

Jani is a 15 year veteran of the software industry. He's currently available for consulting

  1. 11 Responses to “Database helper for PHPUnit”

  2. Thanks for this blog post — I’ve been spending so much time with setting up Zend_Test that I haven’t had a chance to look into ways to automate the database side of it (apart from developing some methodologies for setting up your database schema). I’ll be linking to this post in the coming weeks as questions pop up.

    By Matthew Weier O'Phinney on Aug 27, 2008

  3. Glad you found this useful – I was debating whether or not to publish this, since I’m still a bit new to testing stuff “properly” :D

    By Jani Hartikainen on Aug 27, 2008

  4. Interesting approach – very helpful when needing to do end-to-end testing. While end-to-end testing is definitely useful, it’s worth mentioning that mock objects are another way to create tests without even needing a real database. I haven’t done this yet with Zend_Db (and I don’t use Doctrine) or else I’d give more details on the concept ;-)

    By Bradley Holt on Aug 27, 2008

  5. Mock objects would probably be the way to go, but it would require a slightly different approach when fetching the objects from the “database” I believe – adding some extra work. For now the stuff I’m doing are fine without that much finesse =)

    By Jani Hartikainen on Aug 27, 2008

  6. Hello Jani,

    Thank you for so usable post :)

    But probably I have lost something…
    Trying to get this work with Front Controller Plugin, just like here: http://framework.zend.com/manual/en/zend.test.phpunit.html

    Everything looks great except one thing.
    $this->initDb method called from routeStartup which invoked much later than DbResetListener->startTest, i.e. my in-memory database overridden by regular database :D

    Any idea?

    Thanks.

    By Wik on Sep 18, 2008

  7. You probably should disable plugins when testing features that don’t require those.

    By Jani Hartikainen on Sep 18, 2008

  8. Erm… I have only one and it’s bootstrap plugin.

    Sample of test case I am using:
    class Pages_PageControllerTest extends Zend_Test_PHPUnit_ControllerTestCase implements DbTest_Interface {
    public function setUp()
    {
    $this->bootstrap = array($this, ‘appBootstrap’);
    parent::setUp();
    }

    public function appBootstrap()
    {
    $this->frontController->registerPlugin(new Bugapp_Plugin_Initialize(‘development’));
    }
    }

    Than adding this test case to suite under AllTests and running in cli. The tests works fine when disabling database initialization in bootstrap plugin, i.e.
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
    //$this->initDb();
    $this->initHelpers();
    $this->initView();
    $this->initPlugins();
    $this->initRoutes();
    $this->initControllers();
    }

    I though to do the trick to disable database when testing, but I feel it’s will be other way… not so good as you described…

    Thank you for so quick reply!

    p.s.
    Just a bit confused what is the best place to init first Doctrine’s connection.

    BTW, where you initializing your first Doctrine connection? :)

    By Wik on Sep 18, 2008

  9. Have some solution, not so elegant, but works fine with bootstrap plugin(aka Application Initialization Plugin):

    class Pages_PageControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
    {
    public function appBootstrap()
    {
    $controller = $this->getFrontController();
    $controller->registerPlugin(new Bugapp_Plugin_Initialize(‘development’));
    Doctrine_Manager::connection(new PDO(‘sqlite::memory:’));
    Doctrine::loadModels(dirname(__FILE__) . ‘/../models/generated’);
    Doctrine::loadModels(dirname(__FILE__) . ‘/../models’);
    Doctrine::createTablesFromModels();
    Doctrine::loadData(dirname(__FILE__) . ‘/../doctrine/data/fixtures’);
    }

    public function setUp()
    {
    $this->bootstrap = array($this, ‘appBootstrap’);
    parent::setUp();
    }

    public function tearDown()
    {
    Doctrine_Manager::getInstance()->closeConnection(Doctrine_Manager::connection());
    }

    // …
    }

    By Wik on Sep 18, 2008

  10. Oh, I forgot to say that I have moved $this->initDb() from routeStartup to constructor of my Bugapp_Plugin_Initialize. In other case my test case above had same problem.

    Thanks.

    By Wik on Sep 18, 2008

  1. 2 Trackback(s)

  2. Aug 27, 2008: Database Helper for PHPUnit « PHP::Impact ( [str Blog] )
  3. Sep 1, 2008: PHP Unit testing | NeXt

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)