Code: Setting up unit testing in Zend Framework with multiple databases

I’m currently working on a Zend Framework project for EFI that will enable customers to obtain a coupon for a discount on showerheads at Lowe’s retailers. The idea is that the number of showerheads they’re able to purchase must be limited; the sum of how many they purchase on our e-commerce site and and how many they purchase at Lowe’s must be limited. As part of this project, I’m required to work with two separate databases; the osCommerce database and a new database to track barcodes on the coupons. I found myself needing to rewrite the purchase rules that we’d created in osCommerce in a ZF model. The rules are complex, compounded by the fact that we can’t use the customer IDs in osCommerce, we’re only able to use the customer’s utility account number, as well as the fact that the purchase rules limit purchases by category and not product, and a product can be in multiple categories… well, you get the idea.

Since the rules are complex, and testing the methods for proper function was proving to be maddeningly difficult in the way that I usually test them, I decided to try to use unit testing for the model. I worked off this excellent blog post by Matt Turland, but found myself needing to modify the code to work with multiple databases.

First, let me post part of my application.ini that establishes the resources necessary for multiple database usage with unit tests (notice that I’m using the _testing naming convention to indicate that these databases are in fact schema-only copies of the databases that the ‘regular’ app will use):

[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
 
resources.multidb.db1.adapter  = "PDO_MYSQL"
resources.multidb.db1.dbname   = "db1_testing"
resources.multidb.db1.host     = "127.0.0.1"
resources.multidb.db1.username = "username"
resources.multidb.db1.password = "secret"
 
resources.multidb.db2.adapter  = "PDO_MYSQL"
resources.multidb.db2.dbname   = "db2_testing"
resources.multidb.db2.host     = "127.0.0.1"
resources.multidb.db2.username = "username"
resources.multidb.db2.password = "secret"

Matt proposed using an abstract base class for all unit tests. I won’t replicate his entire post here, but I will describe the changes I made.

The abstract base test class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php
 
abstract class AbstractModelTest extends Zend_Test_PHPUnit_DatabaseTestCase
{
    /**
     * Database connection
     * @var Zend_Test_PHPUnit_Db_Connection
     */
    protected $_db;
 
    /**
     * Instance of the model to be tested, instantiated by this class
     * @var object
     */
    protected $_model;
 
    /**
     * Name of the model class to be tested, intended to be overridden by
     * subclasses for use by this class when instantiating the model
     * @var string
     */
    protected $_modelClass;
 
    /**
     * Path to the directory for data set fixture files
     * @var string
     */
    protected $_filesDir;
 
    /**
     * Which database to use for multidb
     * @var string
     */
    protected $_multidbDatabase;
 
    /**
     * Initializes the model.
     * @return void
     */
    public function setUp()
    {
        $this->checkMultidbDatabaseName();
        $this->getConnection();
        $this->_filesDir = dirname(__FILE__ . "/_files/" . $this->_modelClass);
        $this->_model = new $this->_modelClass($this->getAdapter());
        parent::setUp();
    }
 
    public function checkMultidbDatabaseName()
    {
        if (empty($this->_multidbDatabase)) {
            trigger_error('$this->_multiDbDatabase must be set for these tests
                           to be meaningful.');
        }
    }
 
    /**
     * Implements PHPUnit_Extensions_Database_TestCase::getConnection()
     * @return Zend_Test_PHPUnit_Db_Connection
     */
    protected function getConnection()
    {
        if(empty($this->_db)) {
            $app = new Zend_Application(APPLICATION_ENV, APPLICATION_CONFIG);
            $app->bootstrap();
            $options = $app->getOptions();
            // This needed to be modified to work with multidb
            $schema = $options['resources']['multidb'][$this->_multidbDatabase]['dbname']; 
            $db = $app->getBootstrap()->getPluginResource('multidb')->getDb();
            $this->_db = $this->createZendDbConnection($db, $schema);
        }
        return $this->_db;
    }
 
    /**
     * Implements PHPUnit_Extensions_Database_TestCase::getDataSet().
     * @return PHPUnit_Extensions_Database_DataSet_IDataSet
     */
    protected function getDataSet()
    {
        return $this->createXmlDataSet(dirname(__FILE__) . '/_files/seed.xml');
    }
}

The major differences in this class from what Matt described are the additional property called $_multidbDatabase and some modifications to the setUp() and getConnection() methods. Matt had the bootstrap for the Zend_Application instance happening in the getConnection() method; unfortunately, I have classes in the library/Mycompany/foo/bar/baz directories, and PHPUnit wasn’t able to find them. I kept getting fatal “class not found” errors; the Zend autoloader didn’t know where to look for the classes. Which was puzzling, since I had the following line in my application.ini (which was used to configure Zend_Application):

autoloaderNamespaces[] = "Mycompany"

When I ran my ZF application normally, the classes were always found fine. It turns out that the abstract class attempts to instantiate the model it is intended to test as part of setUp(); as the Zend_Application hadn’t been bootstrapped yet, so the autoloader didn’t know about the additional namespace to search in. So I moved the Zend_Application bootstrap to the getConnection() method, and added a call to getConnection() in setUp().

The other change here was how a reference to the database schema is defined. Since I’m using the multidb resource plugin to Zend_Application instead of the db plugin, line 68 needed to be modified to reflect the different resource parameters in application.ini. You’ll also notice use of the _multidbDatabase property there; a method to establish that it’s been set before the bootstrap has been added as well. trigger_error() will cause a unit test to error out when called.

Also, phpunit.xml had to be modified:

<phpunit colors="true" bootstrap="./TestHelper.php">
    <testsuite name="Model Test Suite">
        <directory>./</directory>
    </testsuite>
 
    <filter>
        <whitelist>
            <directory suffix=".php">../application/</directory>
            <exclude>
                <directory suffix=".phtml">../application/</directory>
            </exclude>
            <directory suffix=".php">../library/Mycompany</directory>
 
        </whitelist>
    </filter>
    <logging>
        <log highlowerbound="80" lowupperbound="50" highlight="true" yui="true" 
             charset="UTF-8" target="./log/report" type="coverage-html">
             <log target="./log/testdox.html" type="testdox-html">
 
             </log>
        </log>
    </logging>
</phpunit>

A <directory> tag had to be added in this case to reflect the additional directory. (Looking at this now, I’m not sure it’s absolutely necessary. Comments are welcome.)

The following is a sample unit test case for this setup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require_once dirname(__FILE__) . "/AbstractModelTest.php";
 
class MyTest extends AbstractModelTest
{
    /**
     * Model class specification
     * @var string
     */
    protected $_modelClass = "Mycompany_Model_Foo_Bar";
 
    /**
     * Which database to use
     * @var string
     */
    protected $_multidbDatabase = 'db1';
}

This would then include various tests intended to test methods within the model.

Please note that this setup will require a file called seed.xml in a _files directory in the same directory as AbstractModelTest and MyTest, representing information to populate a database for testing purposes. The contents of this file will be dictated by the schema of your database and this XML file format.

Hopefully this helps someone out who needs to use more than one database in a Zend Framework application where they would like to use unit testing on their models.

February 10, 2011 В· zburnham В· One Comment
Posted in: Uncategorized

One Response

  1. serg - April 18, 2012

    Hi Zachary, I had the same problem but I need to use 2 db-s at the same time. Actually I believe it’s not very good practice but project was old and I need a way to add tests before refactoring. I did it in this way
    http://radzserg.blogspot.com/2012/04/zendtestphpunitdatabasetestcase-with.html

    Maybe it will be helpfull

Leave a Reply