Thursday, August 28, 2014

PHPUnit in Zend Framework 2

Hi,

Process of setup PHPUnit testing in Zend Framework-2:

NOTE: Before starting to describe the process of PHPUnit i assuming that readers are aware of Zend Framework-2.

Step 1: PHPUnit Installation with the help of "Pear"
# pear config-set auto_discover 1
# pear install pear.phpunit.de/PHPUnit

Step 2: Create "test" directory inside your module.

Step 3: create phpunit.xml, TestConfig.php and Bootstrap.php file inside the "test" directory.

phpunit.xml File
<?xml version="1.0" encoding="UTF-8"?> 
<phpunit bootstrap="Bootstrap.php">
 <php>
  <!--server name="SERVER_PORT" value="80"/ -->
   </php>
    <testsuites>
        <testsuite name="Demo PHPUnit">
            <directory>./ApplicationTest</directory>
        </testsuite>
    </testsuites>
</phpunit>

TestConfig.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
return array(
    'modules' => array(
        // Other modules needed
        'Application',
    ),
    'module_listener_options'   => array(
        'config_glob_paths' => array(
            '../../../config/autoload/{,*.}{global,local}.php',
        ),
        'module_paths'      => array(
            'module',
            'vendor',
        ),
    ),
);


Bootstrap.php

  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
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?php
namespace ApplicationTest; // our namespace
 
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use Zend\Stdlib\ArrayUtils;
use RuntimeException;
use Zend\Session\Container;
 
error_reporting(E_ALL | E_STRICT);
chdir(__DIR__);
 
class Bootstrap
{
    protected static $serviceManager;
    protected static $config;
    protected static $bootstrap;
 
    public static function init()
    {
        // Load the user-defined test configuration file, if it exists; otherwise, load
        if (is_readable(__DIR__ . '/TestConfig.php')) {
            $testConfig = include __DIR__ . '/TestConfig.php';
        } else {
            $testConfig = include __DIR__ . '/TestConfig.php.dist';
        }
 
        $zf2ModulePaths = array();
 
        if (isset($testConfig['module_listener_options']['module_paths'])) {
            $modulePaths = $testConfig['module_listener_options']['module_paths'];
            foreach ($modulePaths as $modulePath) {
                if (($path = static::findParentPath($modulePath)) ) {
                    $zf2ModulePaths[] = $path;
                }
            }
        }
         
        $zf2ModulePaths  = implode(PATH_SEPARATOR, $zf2ModulePaths) . PATH_SEPARATOR;
        $zf2ModulePaths .= getenv('ZF2_MODULES_TEST_PATHS') ?: (defined('ZF2_MODULES_TEST_PATHS') ? ZF2_MODULES_TEST_PATHS : '');
 
        static::initAutoloader();
 
        // use ModuleManager to load this module and it's dependencies
        $baseConfig = array(
            'module_listener_options' => array(
                'module_paths' => explode(PATH_SEPARATOR, $zf2ModulePaths),
            ),
        );
 
        $config = ArrayUtils::merge($baseConfig, $testConfig);
 
        $serviceManager = new ServiceManager(new ServiceManagerConfig());
        $serviceManager->setService('ApplicationConfig', $config);
        $serviceManager->get('ModuleManager')->loadModules();
 
        static::$serviceManager = $serviceManager;
        static::$config = $config;
    }
 
    public static function getServiceManager()
    {      
        return static::$serviceManager;
    }
 
    public static function getConfig()
    {
        
        return static::$config;
    }
 
    protected static function initAutoloader()
    {
        $vendorPath = static::findParentPath('vendor');
 
        if (is_readable($vendorPath . '/autoload.php')) {
            $loader = include $vendorPath . '/autoload.php';
        } else {
            $zf2Path = getenv('ZF2_PATH') ?: (defined('ZF2_PATH') ? ZF2_PATH : (is_dir($vendorPath . '/ZF2/library') ? $vendorPath . '/ZF2/library' : false));
 
            if (!$zf2Path) {
                throw new RuntimeException('Unable to load ZF2. Run `php composer.phar install` or define a ZF2_PATH environment variable.');
            }
 
            include $zf2Path . '/Zend/Loader/AutoloaderFactory.php';
 
        }
        
        AutoloaderFactory::factory(array(
            'Zend\Loader\StandardAutoloader' => array(
                'autoregister_zf' => true,
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/' . __NAMESPACE__,
                ),
            ),
        ));
    }
 
    protected static function findParentPath($path)
    {
        $dir = __DIR__;
        $previousDir = '.';
        while (!is_dir($dir . '/' . $path)) {
            $dir = dirname($dir);
            if ($previousDir === $dir) return false;
            $previousDir = $dir;
        }
        return $dir . '/' . $path;
    }
}
 
Bootstrap::init();



Step 4: Create directory for module testing inside the "test" directory. Name should be same as the module name.
EX: if your module name is "Report" then your testing module-name would be "ReportTest"

Step 5: Create Controller directory whose name would be "Controller" inside the Test module i.e. "ReportTest"

Step 6: Create controller class file inside your controller dir, named as your main module controller file-name.
ex: your module controller name as "IndexController" then test-module controller file name should be "IndexControllerTest".

IndexControllerTest.php

 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
<?php
 
namespace ApplicationTest\Controller;
use ApplicationTest\Bootstrap;
use Zend\Mvc\Router\Http\TreeRouteStack as HttpRouter;
use Application\Controller\IndexController;
use Zend\Http\Request;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\RouteMatch;
use Zend\Test\PHPUnit\Controller\AbstractControllerTestCase;
// use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
class IndexControllerTest extends AbstractControllerTestCase
{
    protected $controller;
    protected $request;
    protected $response;
    protected $routeMatch;
    protected $event;
    protected $userMockObj;
    protected $serviceManager;
    
    public function setUp()
    {
        $this->serviceManager = Bootstrap::getServiceManager();
        $this->controller = new IndexController();
        $this->request    = new Request();
        $this->routeMatch = new RouteMatch(array('controller' => 'index'));
        $this->event      = new MvcEvent();
        $config = $this->serviceManager->get('Config');
        $routerConfig = isset($config['router']) ? $config['router'] : array();
        $router = HttpRouter::factory($routerConfig);
         
        $this->event->setRouter($router);
        $this->event->setRouteMatch($this->routeMatch);
        $this->controller->setEvent($this->event);
        $this->controller->setServiceLocator($this->serviceManager);
        $this->userMockObj = $this->getMockBuilder('Application\Model\TestModel')
                                    ->disableOriginalConstructor()
                                    ->getMock();
        $this->setApplicationConfig(
                include __DIR__.'/../../TestConfig.php'
        );
        parent::setUp();
    }
    
    public function testWorkAction() 
    {
        $this->serviceManager->setAllowOverride(true);
        $this->serviceManager->setService('Application\Model\TestModel', $this->userMockObj);
        
        $this->routeMatch->setParam('action', 'work');
        $response = $this->controller->getResponse();
        $result   = $this->controller->dispatch($this->request, $response);
        
        $this->assertEquals(200, $response->getStatusCode());
        
        // Check a ViewModel has been returned
        $this->assertInstanceOf('Zend\View\Model\ViewModel', $result);
        
        // Test against the test data
        $variables = $result->getVariables();
        $this->assertArrayHasKey('name', $variables);
        // Very lazy validation of data ;-)
        $this->assertEquals('OSSCube1', $variables["name"]);

        $this->dispatch('/index');
        $this->assertModuleName('Application');
        $this->assertControllerClass('IndexController');
        $this->assertMatchedRouteName('xyz');
        $this->assertControllerName('Application\Controller\Index');
    }

    public function testMemory() {
        $this->assertGreaterThanOrEqual(699328, memory_get_usage());
    }
}
?>


Now your directory structure would look like as:


Step 7: Run the phpunit inside of the "test" directory. then you will see some output if your test run successfully like as :

#phpunit
PHPUnit 4.1.3 by Sebastian Bergmann.
..
Time: 00:00
OK (2 tests)

Step 8: Some time you will get error, then there would be some code return that has some significant role like as:
. Printed when the test succeeds.
F Printed when an assertion fails while running the test method.
E Printed when an error occurs while running the test method.
S Printed when the test has been skipped.
I  Printed when the test is marked as being incomplete or not yet implemented.
Step 9: To get the html report for the code coverage analysis of your code, it's a 
feature of the phpunit to get the beautiful html formatted dashboard and reports.
To get this feature you have to install the PHP xDebug module.
# sudo pecl install xdebug 
Now, You can create the report by the below command:
# phpunit --coverage-html dir-name
Here, dir-name is the directory where you will put the report content. It will take bit of time to generate the html report. Once your report generated then just open the index.html file in your browser....










To see the code coverage for the tested code:



















In similar way you can get the dashboard on click of dashboard link that contain nice graph(s):
















I hope it will help you to run PHPUnit in Zend Framework-2. If anybody got issue during setup, Please put your comment, i highly appreciate that.. :)


2 comments :

Sarah Maxwell said...

simple at informative great way to learn php development
techniques for bigenners

Tarun Singhal said...

Thanks Sarah....