Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zend Framework 2 Doctrine 2 Module - No Service Locator in Controller Unit Tests

Here is my situation. I am developing a Zend Framework 2 application. I am using Doctrine module to communicate with a MySQL database. It is working fine in the application, I can load the entity manager from the service locator inside my controllers.

But inside my controller unit tests, the service locator doesn't exists and therefor all tests dealing with the database are failing with error message like this:

Fatal error: Call to a member function get() on a non-object in /Users/richardknop/Projects/myproject/module/Api/src/Api/Controller/UserController.php on line 19

I have narrowed the problem down to this method:

$this->getServiceLocator()

Which works in my controllers but returns NULL in unit tests.

This is my application.config.php:

<?php

return array(
    'modules' => array(
        'DoctrineModule',
        'DoctrineORMModule',
        'Api',
    ),
    'module_listener_options' => array(
        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
        'module_paths' => array(
            './module',
            './vendor',
        ),
    ),
);

My local.php file with database connection details:

<?php

return array(
    'doctrine' => array(
        'connection' => array(
            'orm_default' => array(
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                'params' => array(
                    'host'     => 'localhost',
                    'port'     => '3306',
                    'user'     => 'root',
                    'password' => 'root',
                    'dbname'   => 'mydb',
                ),
            ),
        ),
    ),
);

In my module.config.php I have:

'doctrine' => array(
    'driver' => array(
        __NAMESPACE__ . '_driver' => array(
            'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
            'cache' => 'array',
            'paths' => array(__DIR__ . '/../src/' . __NAMESPACE__ . '/Entity')
        ),
        'orm_default' => array(
            'drivers' => array(
                __NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver'
            ),
        ),
    ),
),

And in my controllers I have this method to get the entity manager:

private $_em;

private function _getEntityManager()
{
    if (null === $this->_em) {
        $this->_em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
    }
    return $this->_em;
}

Now my unit tests. Here is the bootstrap file:

<?php

chdir(dirname(__DIR__));
include __DIR__ . '/../init_autoloader.php';
return Zend\Mvc\Application::init(include 'config/application.config.php');

And setUp method of my controller tests looks like:

public function setUp()
{
    $this->_controller = new UserTokenController;
    $this->_request = new Request;
    $this->_routeMatch = new RouteMatch(array('controller' => 'user'));
    $this->_event = new MvcEvent();
    $this->_event->setRouteMatch($this->_routeMatch);
    $this->_controller->setEvent($this->_event);
}

An example controller unit test then looks like:

public function testGetListHttpStatusCode()
{
    $response = $this->_controller->dispatch($this->_request);
    $this->assertEquals(405, $response->getStatusCode());
}

All tests that are not connecting to the database are passing. Tests where I am using entity manager are failing though with this error:

Fatal error: Call to a member function get() on a non-object in /Users/richardknop/Projects/myproject/module/Api/src/Api/Controller/UserController.php on line 19

This is line 19:

$this->_em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');

And the reason it's failing is that $this->getServiceLocator() returns NULL.

Any ideas how to solve this issue?

like image 354
Richard Knop Avatar asked Sep 14 '12 21:09

Richard Knop


2 Answers

Just extend your controller from \Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase

It's init service locator and do a lot of other Controller's job. As well become available controller asserts, response asserts, CSS asserts etc. Details with examples look here http://framework.zend.com/manual/2.1/en/modules/zend.test.phpunit.html

like image 66
Igor Vizma Avatar answered Nov 15 '22 14:11

Igor Vizma


dont' do this ins your bootstrap.php

return Zend\Mvc\Application::init(include 'config/application.config.php');` in your 

but something like this:

chdir('/path/to/application/root');

// if you're using composer to install zf2
include_once 'vendor/autoload.php';
// if not using composer initialize your custom autoloading here

$configuration = include('config/application.config.php');
$serviceManager = new ServiceManager(new ServiceManagerConfig(
    isset($configuration['service_manager']) ? $configuration['service_manager'] : array()
));
$serviceManager->setService('ApplicationConfig', $configuration);
$serviceManager->setFactory('ServiceListener', 'Zend\Mvc\Service\ServiceListenerFactory');

$moduleManager = $serviceManager->get('ModuleManager');
$moduleManager->loadModules();
$serviceManager->setAllowOverride(true);

$application = $serviceManager->get('Application');
$event  = new MvcEvent();
$event->setTarget($application);
$event->setApplication($application)
    ->setRequest($application->getRequest())
    ->setResponse($application->getResponse())
    ->setRouter($serviceManager->get('Router'));

you could then create static methods in your test-case to set and get the event...

namespace My;

class TestCase extends \Some\Basic\TestCase
{
    protected static $_mvcEvent;

    public static function setMvcEvent(MvcEvent $e) {

        self::$_mvcEvent = $e;
    }

    public static function getMvcEvent() {

        return self::$_mvcEvent;
    }
}

...and inject it from the bootstrap.php file

My\TestCase::setMvcEvent($event); 

and in your setUp method of the test simply do something like this

$this->_controller = new UserTokenController;
$this->_controller->setEvent(self::getMvcEvent());
$this->_controller->setServiceLocator(
    self::getMvcEvent()->getApplication()->getServiceManager()
);

also inject request etc. from event->application into controller here...

like image 44
Andreas Linden Avatar answered Nov 15 '22 15:11

Andreas Linden