Search code examples
phpzend-framework2phpunitservice-locator

getServiceLocator returning Null under PHPUnittests


I'm trying to test a simple controller that authenticates a user using the LdapAdapter and using the 'ldap' array from the configuration of the Application, but phpunit is returning the following error:

Fatal error: Uncaught Error: Call to a member function get() on null in /var/www/html/app/module/Auth/src/Auth/Controller/AuthController.php:53
Stack trace:
#0 /var/www/html/app/module/Auth/test/AuthTest/Controller/AuthControllerTest.php(37): Auth\Controller\AuthController->authenticate('myuser', 'mypassword')
#1 [internal function]: AuthTest\Controller\AlbumControllerTest->testLoginAction()
#2 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestCase.php(863): ReflectionMethod->invokeArgs(Object(AuthTest\Controller\AlbumControllerTest), Array)
#3 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestCase.php(741): PHPUnit_Framework_TestCase->runTest()
#4 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestResult.php(608): PHPUnit_Framework_TestCase->runBare()
#5 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestCase.php(697): PHPUnit_Framework_TestResult->run(Object(AuthTest\Controller\AlbumControllerTest))
#6 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestSuite.php(733): PHPUnit_Framework_TestCase- in /var/www/html/app/module/Auth/src/Auth/Controller/AuthController.php on line 53

My Controller is the following:

<?php

namespace Auth\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\Authentication\Adapter\Ldap as AuthAdapter;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Result;
use Auth\Form\AuthForm;
use Auth\Model\Auth;


class AuthController extends AbstractActionController
{

    public function loginAction()
    {

        $form = new AuthForm();

        $request = $this->getRequest();
        if ($request->isPost()) {
            $auth = new Auth();

            $form->setInputFilter($auth->getInputFilter());
            $form->setData($request->getPost());

            if ($form->isValid()){
                $auth->exchangeArray($form->getData());

                $values = $form->getData();

                $result = $this->authenticate($values['username'], $values['password']);

                switch($result->getCode()) {

                    case Result::SUCCESS:
                        return $this->redirect()->toRoute('home');
                        break;

                    case Result::FAILURE:
                        break;
                    }
                }

            }
        return array('form' => $form);

        }

    public function authenticate($username, $password){

        $options = $this->getServiceLocator()->get('Config');

        $authAdapter = new AuthAdapter($options['ldap'],
            'username',
            'password');

        $authAdapter
            ->setIdentity($username)
            ->setCredential($password);

        $auth = new AuthenticationService();

        $result = $auth->authenticate($authAdapter);

        return $result;
    }

    private function debug($var){
        echo '<pre>';
        var_dump($var);
        echo '</pre>';
        exit();
    }
}

The TestCase:

namespace AuthTest\Controller;

use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
use Zend\Authentication\AuthenticationService;
use Auth\Controller\AuthController;


class AuthControllerTest extends AbstractHttpControllerTestCase
{
    protected $traceError = true;

    public function setUp()
    {
        $this->setApplicationConfig(
            include '/var/www/html/app/config/application.config.php'
        );
        parent::setUp();
    }

    public function testLoginAction()
    {
        #Basic Access to the page
        $this->dispatch('/login');
        $this->assertResponseStatusCode(200);

        $data = array(
            'identity' => 'myuser',
            'credential' => 'mypassword',
        );

        $auth = new AuthController();
        $auth->authenticate($data['identity'], $data['credential']);

        $identity = new AuthenticationService();
        $this->assertEquals($data['identity'], $identity->getIdentity());
    }
}

PHPUnittest's BootStrap:

<?php

namespace AuthTest;

use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use RuntimeException;

error_reporting(E_ALL | E_STRICT);
chdir(__DIR__);

/**
 * Test bootstrap, for setting up autoloading
 */
class Bootstrap
{
    protected static $serviceManager;

    public static function init()
    {
        $zf2ModulePaths = array(dirname(dirname(__DIR__)));
        if (($path = static::findParentPath('vendor'))) {
            $zf2ModulePaths[] = $path;
        }
        if (($path = static::findParentPath('module')) !== $zf2ModulePaths[0]) {
            $zf2ModulePaths[] = $path;
        }

        static::initAutoloader();

        // use ModuleManager to load this module and it's dependencies
        $config = array(
            'module_listener_options' => array(
                'module_paths' => $zf2ModulePaths,
            ),
            'modules' => array(
                'Auth'
            )
        );

        $serviceManager = new ServiceManager(new ServiceManagerConfig());
        $serviceManager->setService('ApplicationConfig', $config);
        $serviceManager->get('ModuleManager')->loadModules();
        static::$serviceManager = $serviceManager;
    }

    public static function chroot()
    {
        $rootPath = dirname(static::findParentPath('module'));
        chdir($rootPath);
    }

    public static function getServiceManager()
    {
        return static::$serviceManager;
    }

    protected static function initAutoloader()
    {
        $vendorPath = static::findParentPath('vendor');

        if (file_exists($vendorPath.'/autoload.php')) {
            include $vendorPath.'/autoload.php';
        }

        if (! class_exists('Zend\Loader\AutoloaderFactory')) {
            throw new RuntimeException(
                'Unable to load ZF2. Run `php composer.phar install`'
            );
        }

        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();
Bootstrap::chroot();

In the functional tests it works as expected, but on php unittests the error occurs in the line 53 '$options = $this->getServiceLocator()->get('Config');'.

So, how can use or set ServiceLocator to work with phpunittests?


Solution

  • After so much struggling on this doubt and other tests which a controller that uses an ServiceLocator, I figure that the correct way to test a controllers Action is to Mock the object or/and his methods.

    I don't feel totally comfortable with this solution, but for now, this is what it is.

    An other feel is that mocking an behaviour of the code is something like that breaks the DRY principle: instead of use the real code I just create a mock that half behaves like it :S

    Anyway, the following code does the job for me:

        $authControllerMock = $this->getMockBuilder('Auth\Controller\AuthController')
            ->disableOriginalConstructor()
            ->getMock();
    
        $authControllerMock->expects($this->any())
            ->method('authenticate')
            ->will($this->returnValue(true);
    
        $serviceManager = $this->getApplicationServiceLocator();
        $serviceManager->setAllowOverride(true);
        $serviceManager->setService('Auth\Controller\Auth', $authControllerMock);
    
        $this->dispatch('/usermgr');
    
        self::assertMatchedRouteName('usermgr');
        self::assertControllerClass('AuthController');
        self::assertControllerName('auth\controller\auth');
        self::assertResponseStatusCode('200');