Zend Framework

PHPUnit with Zend Framework – Unit Testing

Before, I used SimpleTest for unit testing. However, given the more advanced PHP 5.xx features and coding standards, I completely switch to PHPUnit. Moreover, PHPUnit’s coding convention is similar to Zend Framework and PEAR.

Directory Structure

Since PHPUnit is already integrated to Zend Framework via Zend_Test packages, we will expect everything will work out of the box. Let us start with the directory structure. The suggested directory structure and samples on the web about setting up PHPUnit on Zend Framework does not work for me, perhaps there are some configurations missing. However, I just go on with my modifications.

Create a tests directory under the project directory, the same level as application, public and library as suggested by Zend Framework tutorials. The my style, create a directory under it, called TestSuites. Create a bootstrap file, phpunit.xml file and log directory. Under log directory, create a directory named coverage.

As you’ve noticed above, there are directories named application and library directories. That is because it is the one suggested on the tutorial. I didn’t removed them though they are all empty.

Next, we will create the bootstrap file. As you’ve seen on the image above, there are two bootstrap files, Bootstrap.php and BootstrapZend.php. Bootstrap.php was my first bootstrap but I created another one which is recommended by the tutorial. This is the content of BootstrapZend.php:

define('INDEX_PATH', str_replace('\\', '/', realpath(dirname(__FILE__) . '/../public')));
define('APPLICATION_PATH', str_replace('\\', '/', realpath(INDEX_PATH . '/../application')));
define('LIBRARY_PATH', str_replace('\\', '/', realpath(INDEX_PATH . '/../library')));
set_include_path(
		LIBRARY_PATH
		. PATH_SEPARATOR
		. get_include_path()
	);

/**
 * Salt for general hashing (security)
 */
define('GENERIC_SALT', 'asdDSasd4asdAd1GH4sdWsd1');

// Define application environment
define('APPLICATION_ENV', 'development');

// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));

/** Zend_Application */
require_once 'Zend/Application.php';

It is just a simplified version of the regular index.php for most Zend Framework projects, trimmed down so that it will not dispatch the front controller.

Next, we need to configure phpunit.xml so that we can just run phpunit on this directory and that phpunit will execute tests automatically when tests are found under the TestSuites directory.

<phpunit colors="false">
    <testsuite name="ApplicationTestSuite">
        <directory>./TestSuites</directory>
    </testsuite>
    <filter>
        <whitelist>
            <directory suffix=".php">../application</directory>
            <exclude>
                <directory suffix=".phtml">../application/views</directory>
                <file>../application/Bootstrap.php</file>
            </exclude>
        </whitelist>
    </filter>
    <logging>
        <log type="coverage-html" target="./log/coverage" charset="UTF-8"
             yui="true" highlight="false" lowUpperBound="35" highLowerBound="70"/>
    </logging>
</phpunit>

The phpunit.xml file just configures the phpunit runtime so that it will look under TestSuites directory, exclude views scripts and store logs under logs/coverage.

As you’ve noticed, under TestSuites directory, there are directories named controllers, Fms and models. They mimic the directory structure under the application directory except that Fms comes from the library directory. It is just a way to organize our test cases although you can put all test cases on a single directory as you wish without modifying paths on some include statements.

Sample Test

Now let us try a sample test case. I have a sample test inside tests/TestSuites/models/CodeTest.php. Please note that all test cases must end with Test suffix.

require_once 'BootstrapZend.php';
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';

class CodeTest extends Zend_Test_PHPUnit_ControllerTestCase
{	
	public function setup()
	{
        // Assign and instantiate in one step:
        $this->bootstrap = new Zend_Application(
            'testing', 
            APPLICATION_PATH . '/configs/application.ini'
        );
        parent::setUp();
	}
	
	public function testObject()
	{
		$codeModel = new Default_Model_Code;
		$this->assertType('Default_Model_Code', $codeModel);
		
		return $codeModel;
	}
    }

Take a look at the require_once statements. Instead of messing around require_once path, phpunit allows you to use the topmost path of your tests so that you will not have to resolve relative paths.

Instead of require_once ‘../../BootstrapZend.php’, we just use require_once ‘BootstrapZend.php’. To make it simple, we always refer to the path where phpunit.xml is located.

Since we have not activated autoloading feature of Zend Framework, we need to manually require the file ‘Zend/Test/PHPUnit/ControllerTestCase.php’ to before starting the main test case class.

Before any tests are run, we ensure that the Zend Framework environment is properly setup, eg: autoloading. That is why we have the setup() function in our sample test case. The actual test is done on all functions that has the test prefix, thus, in our example it is testObject.

$codeModel = new Default_Model_Code; 
$this->assertType('Default_Model_Code', $codeModel); 

The assertion above simply asserts that the object’s type must be a Default_Model_Code object.

Run Test

Open a console / command line / terminal / whatever you call it in your native OS / platform. Go to your tests directory and type “phpunit” assuming that phpunit is properly setup. You should get a result something like this:

The output above may show different to your tests because I run it against more than one test cases. Now, to create several test cases, you only need to put the require_once statements as written above and also don’t forget to include the setup function.

That’s it!

1 thought on “PHPUnit with Zend Framework – Unit Testing”

Leave a reply

Your email address will not be published. Required fields are marked *