Skip to Content

Unit Testing with Zend Framework 1.11 and PHPUnit

Either I failed in my Google-Foo, or there is not a lot of current documentation on setting up Unit Testing for Zend Framework 1.11. So, having worked through the process, here's my approach.

Most of my notes are based on the excellent ZendCast Unit Testing with the Zend Framework with Zend_Test and PHPUnit. While this video is very educational and helpful, it was made with pre 1.8 versions of Zend Framework. There have been many changes since then - to ZF, PHPUnit, and even the underlying systems. Some of those changes break the routine defined in the video. Also, the video assumes everything is done right, and doesn't necessarily handle troubleshooting.

Here then is the method I found to set up unit testing via PHPUnit, in Zend Framework 1.11.5. Each step is defined further below.

  1. Install PHPUnit
  2. Create your ZF Project (via ZF tool)
  3. Setup your phpunit.xml file
  4. Creating your testing bootstrap.php file
  5. Create a class that handles the initial setup of the testing environment. (ControllerTestCase)
  6. Test the setup so far - should be working with only one expected error.
  7. Define an initial test
  8. Test your test
  9. Enter Unit Testing nirvana. (You're done and can focus on your application testing now)
  10. Final Points
  11. Resources

NOTE: We'll be using the command line for this (assuming a *nix box). Adjust the instructions appropriately to match your environment.

Install PHPUnit

On Ubuntu, I used

sudo apt-get install phpunit

However, this installs a version that is at least one full generation old (as of today at least). To get the current version, use PEAR. Details for this can be found at http://www.phpunit.de/manual/3.0/en/installation.html.

Create your ZF Project

We'll assume you already have an environment set up to handle ZF based applications. So, either use an existing project for the remaining steps, or create a new project via the ZF tool (i.e. ./zf.sh create project myproject).

Setup phpunit.xml

  • Change into your project directory.
  • Change into the tests directory. (which is at the same level as the oh-so critical application directory)
  • Edit the phpunit.xml file. It is blank by default. Add the following:
    
        
            ./
        
        
        
            
                ../application/
                
                    ../application/
                    ../application/Bootstrap.php
                    ../application/controllers/ErrorController.php
                
            
        
        
        
            
            
        
    
    

    Feel free to tweak this to match your specific needs, but this is a pretty basic and standard setup, I think.

  • The bootstrap attribute for the phpunit tag needs to point to the TESTING bootstrap file, not the main application Bootstrap.php class.
  • We need to add a testsuite. This defines what tests we'll run. If you find you need multiple testsuites, add a testsuites tag (notice the S), and then put the individual testsuite tags into the new testsuites container tag.
  • The filter defines what files we'll test, and whithin that definition, what files we should not test. (i.e. All files in the ../application directory, but not the Bootstrap.php file, the ErrorController.php file, or any of the .phtml files).
  • Finally we define how we want logging done.
  • (I know I was brief in these descriptions, but the PHPUnit Manual is the best place to look for more info.)
  • One word of warning - if your file is not valid XML (i.e. you forget an ending tag, like say on the log tags...) you will get an error telling you the phpunit.xml file could not be loaded. I understand that newer versions of PHPUnit report the error more clearly, but I'm sticking with what the apt-get package manager gives me (for now).

Create the testing bootstrap.php file

  • Change into your project directory
  • Change into the tests/application directory.
  • Edit the bootstrap.php file. The default file is empty. If the file does not exist, create it. Add the following:
    <?php
    error_reporting(E_ALL | E_STRICT);
    
    // Define path to application directory
    defined('APPLICATION_PATH')
        || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../../application'));
    
    // Define application environment
    defined('APPLICATION_ENV')
        || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'testing'));
    
    // Ensure library/ is on include_path
    set_include_path(implode(PATH_SEPARATOR, array(
        realpath(APPLICATION_PATH . '/../library'),
        get_include_path(),
    )));
    
    require_once 'Zend/Application.php';
    require_once 'ControllerTestCase.php';
    
  • This is mostly identical to the public/index.php file for our project. There are a few important differences though.
    First, the APPLICATION_PATH declaration has changed - we need an extra "../" in the path. Next the APPLICATION_ENV declaration
    should default to "testing", not "production", in this case. And finally, we don't actually want to run code just yet, so the bootstrap methods are not called.

Create the class (ControllerTestCase) to set up the testing environment

  • Change into your project directory
  • Change into the tests/application directory.
  • Create the file ControllerTestCase.php. Add the following code to it:
    <?php
    require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';
    
    class ControllerTestCase extends Zend_Test_PHPUnit_ControllerTestCase
    {
        /**
         * @var Zend_Application
        */
        protected $application;
        
        public function setUp() {
            $this->bootstrap = array($this, 'appBootstrap');
            parent::setUp();
        }
        
        public function appBootstrap() {
            $this->application = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
            $this->application->bootstrap();
        }
    }
    
  • This file needs the require_once() command because the autoloader is not enabled yet. That is done in our appBootstrap() function which is in this class. So the Zend_Test_PHPUnitControllerTestCase class will not be found if we do not include the file manually.
    AFTER this setup code is run, the autoloader will handle loading any other classes for us automatically - just like a regular Zend Framework app.
  • The setUp() method populates our internal bootstrap variable. Next it ensures that the appBootstrap method is called. Finally the parent class's setup routine is called.
  • The appBootstrap() method loads our Zend Application object, making use of the main application configuration file. Then it executes the bootstrap routines for the application. This enables the autoloader, and any other items that the application may need (plugins, logging, etc.).

Test the basic PHPUnit environment

Finally, we should be set to start creating tests. But before we go too far down that path, let's make sure everything is working as expected.

  • Change into your project directory
  • Change into the tests directory.
  • Execute the command phpunit --configuration phpunit.xml
  • If all goes well you should see output something like this:
    PHPUnit 3.4.13 by Sebastian Bergmann.
    
    Time: 0 seconds, Memory: 9.50Mb
    
    OK (0 tests, 0 assertions)                                                                                              
    
    Generating code coverage report, this may take a moment.PHP Fatal error:  Class 'Zend_Application_Bootstrap_Bootstrap' not found in /home/sgrover/myproject/application/Bootstrap.php on line 4
    
  • The "Zend_Application_Bootstrap_Bootstrap not found" is normal and expected at this stage. We haven't defined any tests to run yet.
  • Another error that may occur can look like this:
    PHP Fatal error:  Uncaught exception 'PHPUnit_Framework_Exception' with message 'Could not load "/home/sgrover/myproject/tests/phpunit.xml".' in /usr/share/php/PHPUnit/Util/XML.php:216

    If you see this, check your phpunit.xml file and make sure it is valid XML.

  • If you see any other errors, resolve them before moving on. Google should be helpful here.
  • It should be noted that if your configuration file is called phpunit.xml and is in the same directory you are calling phpunit from, then the --configuration option can be omitted. That is, you can just use "phpunit" (sans-quotes).

Define our initial test

We are going to setup a stupid simple test just to make sure things work fully. If our test works, then we know we have a valid environment and we can shift focus to building our application specific tests. Any errors we get at that point are not test specific errors, and not envionment specific errors - hopefully.

  • Change into your project directory
  • Change into the tests/application directory.
  • Create a file called Firsttest.php. Add the following code to it:
    <?php
    class FirsttestTest extends ControllerTestCase
    {
        public function setUp() {
            parent::setUp();
        }
        
        public function testCanDoUnitTest() {
            $this->assertTrue(true);
        }
    }
    
  • We define our class. For PHPUnit to use this class for testing, the class name MUST end with Test. The same idea applies to the the method names - those that are actually tests, must also end with "Test". This will allow us to build other support methods as needed that do not actually constitute a test.
  • Our actual application test code will use a similar structure. You'll probably see a protected member variable that represents the Models you want to test, and then populating those member variables in the setUp() method. Then the Test methods would make use of those models (by testing them perhaps...)

Test the Test

Now that we have a formal test defined, we need to see if we pass the test.

  • Change into your project directory
  • Change into the tests directory.
  • Execute the command phpunit --configuration phpunit.xml
  • If all goes well you should see output something like this:
    PHPUnit 3.4.13 by Sebastian Bergmann.                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                   
    .                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                   
    Time: 1 second, Memory: 20.00Mb                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                   
    OK (1 test, 1 assertion)                                                                                                                                                                                                                                                       
    
    Generating code coverage report, this may take a moment.  
    
  • The important line is the "OK (1 test, 1 assertion)". If you don't see that, you probably have an error reported that needs to be resolved.
  • The "Generating code coverage" line is just telling us that an HTML report is being created for our test. You can find that report by opening tests/logs/report/index.html in a browser. It can give you some useful information how thorough and successful your tests are.

Enter Unit Testing Nirvana

Congrats! If you can run that final check without errors, you have functional testing environment. Now you can spend as much time as you'd like creating unit tests for your project! Lucky You!

Final Points

This is only a quick guide to set up . You need to dig deeper into Unit Testing to become really good at it.

One issue I've seen mentioned in multiple cases is to be conscious and careful what you are testing. You should be testing expectations, not code. This means you should never do a test of the line "x = x + 1" to see if it actually does add one to X. (extreme example, but the idea should be apparent.)
Instead, you should be testing things like "when the save routine is called, a record actually goes into the database", or "when we call 'somecontroller/someaction', we should get a 200 response code and the resulting content should have XXXX".

Resources

Some great resources I have found to help with this:

  • Unit Testing with the Zend Framework with Zend_Test and PHPUnit - the video that inspired this blog post. While we covered the environment, Jon covers that and more. While the target ZF version is now dated, the commentary is still very applicable. Jon also goes more in depth on building actual unit tests for your models and controllers.
  • PHPUnit - the obvious and definitive source of information (and code!) for PHPUnit. Including documentation in various formats/languages (scroll down).
  • PHPUnit Manual - a click away from the link above, but still important enough to highlight directly.

Let me know if any of the info I have here is wrong, or if you have more/better resources to add to this list. Happy Unit Testing to ya!