I'm trying to write some test cases for a ZF application I'm writing but I can't seem get past this point.
I have extended the Zend_Test_PHPUnit_ControllerTestCase, adding the following:
public function setUp() {
$this->bootstrap = new Zend_Application('testing',
APPLICATION_PATH . '/configs/application.ini');
parent::setUp();
}
It works, and it initializes the Zend Autoloader, allowing me to load other classes inside my library as well as Zend classes. So I setup a test:
public function testUserUnauthorized() {
$this->dispatch('/api/user');
//Assertions...
}
Sadly, the test never gets past the dispatch. Instead, it gives me an error:
Fatal error: Class 'App_Model_User' not found in ....Action.php
Action.php is a class extending Zend_Controller_Action. In it, there is a function that uses App_Model_User to authenticate the logged in user. I never had to add a require_once since the Autoloader works. That's the strange part: it works through my browser. Just not in PHPUnit.
So I dumped the Autoloaders registered with the Zend Autoloader:
public function testUserUnauthorized() {
print_r(Zend_Loader_Autoloader::getInstance()->getAutoloaders());
exit;
$this->dispatch('/api/user');
}
It shows all my modules, as well as the namespaces for views, controllers, models, etc. I did the same through my browser and the dumps matched.
Zend Autoloader needs App_ added as an autoload prefix for this to work as you expect, something like this:
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace("App_");
Related
I went through the Phalcon docs and got PHPUnit set up and working. However, I'm having trouble bootstrapping my existing services into the unit testing framework.
Currently, I load my config, routes, and services in the /public/index.php. For example, I'm loading the session library:
$di->set(
'session',
function () use ( $config ) {
$session = new Phalcon\Session\Adapter\Redis(
... config ...
);
});
So now, in my application I'd have code that calls $this->session->get( 'user_id' ) to use the session library. I have many of these services -- one for SQL, MongoDB, cookies, etc.
The problem I'm having is correctly loading these services into the unit testing class. The Phalcon docs recommend loading my DI services in the UnitTestCase class (explained here) but I really do not want re-define the same services in this class loader. Additionally, I want to use the components in my application in the same ways; those components rely on the services being lazy-loaded.
So, to attempt this, I include the same /app/config/services.php file in my unit testing TestHelper.php init script thinking that I can just use the same $di object. This works in that my test cases can call $this->di->getSession()->get( 'var' ) but as soon as a component in my app tries to call $this->session->get( 'var' ) it throws an error:
1) Libraries\SessionTest::testGetSet
Undefined property: Libraries\SessionTest::$session
/home/mike/PhalconProject/app/library/Session.php:25
/home/mike/PhalconProject/tests/libraries/SessionTest.php:13
This error is telling me that my application session-management library Session.php is failing when accessing $this->session-> via the dependency injector.
Is there something I'm fundamentally doing wrong here? Do I need to redefine the services in my unit testing class? And if I do need to redefine them, will my application be able to load its services?
So the solution here seems to be structuring the application libraries to statically access the DI services.
The first step was to set the default DI in /public/index.php and /tests/bootstrap.php:
\Phalcon\DI::setDefault( $di );
Now, in the application libraries (like /app/Library/Auth.php or /app/Library/Session.php) the services are accessed statically (more info here in the Phalcon docs):
$session = \Phalcon\DI::getDefault()->getSession();
$session->get( 'user_id' )
...
To make this easier, I set up a base library that all of my application libraries extend. The base library has a method to simplify this call.
namespace Base;
use \Phalcon\DI as DI;
class Library extends \Phalcon\Mvc\User\Component
{
public function getService( $service )
{
$func = "get". ucfirst( $service );
return DI::getDefault()->$func();
}
}
Finally, /app/Library/Session.php would extend this \Base\Library class, and whenever I need the session service in a method I can just call:
$session = self::getService( 'session' );
I have injected all my Phalcon services (which i usually get by calling DI object) using the simple PHPUnit bootstrap.php file (take a look at --bootstrap option of PHPUnit) my bootstrap.php contents is:
<?php
require_once(__DIR__ . '/../app/config/local_config.php');
$config = include __DIR__ . "/../app/config/config.php";
/**
* Read auto-loader
*/
include __DIR__ . "/../app/config/loader.php";
/**
* Read services
*/
include __DIR__ . "/../app/config/services.php";
Thus, in my PHPUnit tests i just call $di->get('MyService');
I'm trying to integrate PHP namespaces into an existing Zend Framework project (v1.12). When I add namespacing at the top of a working controller, it doesn't work anymore and the application throws an Invalid controller class error. Here's my controller definition :
namespace MyProject\Controller;
use MyProject\Controller\MyRestController;
class MyFooController extends MyRestController
{
}
and the init method within the Bootstrap.php:
protected function _initAutoload()
{
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('MyProject');
return $autoloader;
}
Just a guess (have not used ZF for quite some time): Zend will not accept any class as a controller, just those extended from the framework's base controller class. As you don't extend from the frameworks base controller class you see the error.
If that is the reason, take care you initially extended from the base framework controller class or you implemented the needed interface.
namespace MyProject\Controller;
class MyRestController extendes Zend_Framework_Base_Controller_Class_Name_Here
{
...
p.s. the use MyProject\Controller\MyRestController; looks superfluous as that class is in that namespace already. Let's review your code:
namespace MyProject\Controller;
This sets the namespace of the file. That means, non-FQCN will resolve into it. For example:
new MyRestController();
Resolves to the following FQCN:
new MyProject\Controller\MyRestController
Which - oha! - is exactly what you wrote in use:
use MyProject\Controller\MyRestController;
Which means, that this use clause is superfluous, the extend in:
class MyFooController extends MyRestController
Would go to it anyway at first. Because it's the same namespace.
I am facing similar problem now. For me this looks like that Zend cannot properly resolve namespaced controller name. So when I put for example IndexController into namespace \Basic\Controller, it will be not loaded because Zend want to load \IndexController class, which does not exist.
I am thinking about extending standard zend router class, which has method getControllerName.
Then I can set this in bootstrap by:
$router = new \My\Namespaced\Router();
$front = Zend_Controller_Front::getInstance();
$front->setRouter($router);
I didn't tried that code yet but this should work.
I'm trying to implement custom exception classes for a zend project.
I use modules and all my classes are under library.
so a filename DuplicateFileException.php which is under
APPLICATION_PATH "/../library/Abra/Exception" contains
class Abra_Exception_FileNotFoundExcpetion extends Exception {}
class Abra_Exception_MissingFileException extends Exception {}
class Abra_Exception_DuplicateFileException extends Exception {}
class Abra_Exception_FileIOException extends Exception {}
so the ErrorController works fine when i only throw Abra_Exception_DuplicateFileException
because there is indeed a file called DuplicateFileException, but the app breaks when i throw any other than that Exception.
I just can't believe that i have to create a file for each of them.
so how to work around it?
thanks for reading.
Proper zend-way would be implementing your own autoloader class. IT will implement autoload method. You can then use Zend_Autoloader autoloader stack to oad your classes. You just need to have some kind of system/logic in your classname->filename mapping.
class Abra_Autoloader
{
public function autoload($className)
{
if (strpos('Abra_Exception', $className) !== false) {
include $someFile; //faster
}
}
}
//anywhere in bootstrap (preferably in some autoloading section of yours)
// autoloader will load only classes starting with "abra"
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->setFallbackAutoloader(true);
$autoloader->pushAutoloader(array('Abra_Autoloader ', 'autoload'), 'Abra');
Please note that at least teh autoloader class should be Zend_Loader-loadable :)
The Zend autoloader works by mapping requested class names to filesystem paths so you can't make it load a file for a class where the filename does not match.
If you really want to persist down this path, I'd just include the file in your Bootstrap class, eg
protected function _initRequires()
{
require_once 'Abra/Exception/DuplicateFileException.php';
// assuming your "library" folder is on the include path
}
The ZF Docs reference 'Subclassing the Action Controller' (bottom of the page), but don't reference a standard place to put the new Action_Controller class.
Application_Module_Autoloader sets up pats for a bunch of things, but never controllers. I guess putting it on library/APPNAMESAPCE/Action/Contoller would work. But that seems a bit odd since every other application specific file is stored under application/.
The class gets autoloaded like any other class, there isn't a 'standard' place for it as such. So the question becomes, where do you want it to live?
The convention I usually follow in modular applications is to have most stuff in the modules, but register an app namespace and use application/models for 'core' type classes. So in your case, say your app namespace was Wordpress, you'd have:
class Wordpress_Controller_Action extends Zend_Controller_Action
{
}
and the file would live in application/models/Wordpress/Controller/Action.php.
To make this work you'll need application/models on your include path, and you'll want to init the standard autoloader with something like this (in your bootstrap class):
protected function _initAutoloader()
{
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('Wordpress_');
return $autoloader;
}
alternatively you could setup the above in application.ini.
I am starting to write some test cases for controller classes using Zend Framework 1.10.6 and Zend_Test_PHPUnit_ControllerTestCase. I am having problems with one item, which is that while the test cases are running, Zend_Controller_Action_HelperBroker can't find the Layout action helper.
Here are the bare bones of my test case:
require_once 'PHPUnit/Framework.php';
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';
require_once 'controllers/IndexController.php';
class Application_Controllers_IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase {
public $_application;
protected function setUp() {
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp ();
}
public function appBootstrap() {
// Create application, bootstrap, but don't run
$this->_application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$this->_application->bootstrap();
$this->getFrontController()->setParams($this->_application->getOptions())
->addControllerDirectory(APPLICATION_PATH . '/controllers');
}
public function testIndexAction() {
$this->dispatch('/index/index');
$this->assertController('index');
$this->assertAction('index');
}
}
I get an exception when I run the test case:
Zend_Controller_Action_Exception: Action Helper by name Layout not found
When I comment out the two lines in class Zend_Controller_Action_HelperBroker to try to find the source of this around line 368, I get:
Zend_Loader_PluginLoader_Exception: Plugin by name 'Layout' was not found in the registry; used paths:
Zend_Controller_Action_Helper_: Zend/Controller/Action/Helper/
The loading of layout scripts works fine in my application when running, it appears that the correct path or registry for the Zend_Controller_Action_Helper can't be found while running tests under PHPUnit and therefore the Layout plugin can't be loaded.
I have verified that Zend is installed correctly and that Layout.php is in the correct place.
Any ideas?
Del
In you appBootstrap() at the end place this line:
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Layout_Controller_Action_Helper_Layout);
At which point do you add your Layout code?
Remember that the 'boostraping' is different when running a PHPUnit test, and that things that are bootstrapped in your main app might not be when running as a PHPUnit test.
My workaround:
function someAction() {
// workaround for unit tests 'Action Helper by name Layout not found'
if ($this->_helper->hasHelper('layout')) {
$this->_helper->layout->disableLayout(); // disable layouts
}
...