How to test controllers with CodeIgniter? - php

I have a PHP web application built with CodeIgniter MVC framework. I wish to test various controller classes. I'm using Toast for unit testing. My controllers have no state, everything they process is either saved into session or passed to view to display. Creating a mock session object and testing whether that works properly is straightforward (just create a mock object and inject it with $controller->session = $mock).
What I don't know, is how to work with views. In CodeIgniter, views are loaded as:
$this->load->view($view_name, $vars, $return);
Since I don't want to alter CI code, I though I could create a mock Loader and replace the original. And here lies the problem, I cannot find a way to derive a new class from CI_Loader.
If I don't include the system/libraries/Loader.php file, the class CI_Loader is undefined and I cannot inherit from it:
class Loader_mock extends CI_Loader
If I do include the file (using require_once), I get the error:
Cannot redeclare class CI_Loader
Looks like CI code itself does not use require_once from whatever reason.
Does anyone here have experience with unit testing CodeIgniter powered applications?
Edit: I tried to inject a real loader object at run-time into a mock class, and redirect all calls and variables with __call, __set, __get, __isset and __unset. But, it does not seem to work (I don't get any errors though, just no output, i.e. blank page from Toast). Here's the code:
class Loader_mock
{
public $real_loader;
public $varijable = array();
public function Loader_mock($real)
{
$this->real_loader = $real;
}
public function __call($name, $arguments)
{
return $this->real_loader->$name($arguments);
}
public function __set($name, $value)
{
return $this->real_loader->$name = $value;
}
public function __isset($name)
{
return isset($this->real_loader->$name);
}
public function __unset($name)
{
unset($this->loader->$name);
}
public function __get($name)
{
return $this->real_loader->$name;
}
public function view($view, $vars = array(), $return = FALSE)
{
$varijable = $vars;
}
}

Alternatively, you could do this:
$CI =& get_instance();
$CI = load_class('Loader');
class MockLoader extends CI_Loader
{
function __construct()
{
parent::__construct();
}
}
Then in your controller do $this->load = new MockLoader().

My current solution is to alter the CodeIgniter code to use require_once instead of require. Here's the patch I'm going to send to CI developers in case someone needs to do the same until they accept it:
diff --git a/system/codeigniter/Common.php b/system/codeigniter/Common.php
--- a/system/codeigniter/Common.php
+++ b/system/codeigniter/Common.php
## -100,20 +100,20 ## function &load_class($class, $instantiate = TRUE)
// folder we'll load the native class from the system/libraries folder.
if (file_exists(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT))
{
- require(BASEPATH.'libraries/'.$class.EXT);
- require(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
+ require_once(BASEPATH.'libraries/'.$class.EXT);
+ require_once(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
$is_subclass = TRUE;
}
else
{
if (file_exists(APPPATH.'libraries/'.$class.EXT))
{
- require(APPPATH.'libraries/'.$class.EXT);
+ require_once(APPPATH.'libraries/'.$class.EXT);
$is_subclass = FALSE;
}
else
{
- require(BASEPATH.'libraries/'.$class.EXT);
+ require_once(BASEPATH.'libraries/'.$class.EXT);
$is_subclass = FALSE;
}
}

I can't help you much with the testing, but I can help you extend the CI library.
You can create your own MY_Loader class inside /application/libraries/MY_Loader.php.
<?php
class MY_Loader extends CI_Loader {
function view($view, $vars = array(), $return = FALSE) {
echo 'My custom code goes here';
}
}
CodeIgniter will see this automatically. Just put in the functions you want to replace in the original library. Everything else will use the original.
For more info check out the CI manual page for creating core system classes.

I'm impressed by the code you are trying to use.
So now I'm wondering how the 'Hooks' class of CodeIgniter could be of any help to your problem?
http://codeigniter.com/user_guide/general/hooks.html
Kind regards,
Rein Groot

The controller should not contain domain logic, so unit tests make no sense here.
Instead I would test the controllers and views with acceptance tests.

Related

PHP How to create helper function for class method

I would like to create some global functions that make use of an existing class method. I don't have all of the supporting functionality for the getConfig() method shown, but it's irrelevant to my question:
class Init {
public function getConfig($type) {
if (isset($this->config[$type])) {
return $this->config[$type];
}
$file = dirname(__FILE__, 3).'/app/config/'.$type.'.php';
if (!file_exists($file)) {
return false;
}
$contents = include($file);
$this->config[$type] = $contents;
return $this->config[$type];
} // End getConfig
}
// class Init gets instantiated in my bootstrap file
Now, in my helpers.php file:
function app_config($key) {
$init = new Init(); // Is there any better way of doing this?
return $init->getConfig('app')[$key];
}
So, the helper function app_config may be used in 20 places throughout the app for any given request. Not to mention other helper functions that utilize the Init class as well as other classes.
What is the best way to setup these helper functions? Should they be instantiating these classes over and over again? (This makes it extremely difficult with classes which have specific parameters passed to the constructor)

Phalcon library class calling a function within another

Im using phalcon 2.0.0 and i am trying to call a function with in another function but from the same class like shown below, for some reason i get a blank page. And when i comment the calling of 2nd function from first, the page loads properly.
<?php
use Phalcon\Mvc\User\Component;
class Testhelper extends Component {
public function f1($data) {
$tmp = $this->f2($data);
return $tmp;
}
public function f2($data) {
return '5'; // just testing
}
}
And btw im accessing the f1 function by the volt function extender like this
$compiler->addFunction('customfunc', function($resolvedArgs, $exprArgs) {
return 'Testhelper ::f1('.$resolvedArgs.')';
});
if someone could help me, it would be deeply appreciated.
Thanks guys
You are trying to call TestHelper f1() statically in Volt, where your class does not expose that function as a static.
You can change your code like this:
<?php
use Phalcon\Mvc\User\Component;
class Testhelper extends Component
{
public static function f1($data)
{
$tmp = self::f2($data);
return $tmp;
}
public static function f2($data)
{
return '5'; // just testing
}
}
and your Volt function will work. However you have to bare in mind that because you are calling things statically you won't have immediate access to all the di container services that the Component offers like so:
$this->session
$this->db
You will need to modify your code to pick the di container using the getDefault()
Another option is to use the code as you have right now, but register the TestHelper in your di container like so:
$di->set(
'test_helper',
function () {
return new TestHelper();
}
);
and then your volt function will need to change to:
$compiler->addFunction(
'customfunc',
function ($resolvedArgs, $exprArgs) {
return '$this->test_helper->f1('.$resolvedArgs.')';
}
);

Getting started with "Enhance PHP"

I am looking to incorporate a testing framework into a project I am building and came across Enhance PHP which I like but I am having some difficulty finding relevant information on-line since "enhance php" is such a commonly used phrase.
Has anyone worked with this framework that might be able to point me toward some helpful guide? Have you worked with a unit test framework that you think is amazingly better?
Thanks in advance.
In response to Gotzofter, this is the class to be tested:
<?php
include_once('EnhanceTestFramework.php');
class ExampleClass
{
private $OtherClass;
function __construct($mock = null)
{
if ($mock == null)
$this->OtherClass = new OtherExampleClass();
else
$this->OtherClass = $mock;
}
public function doSomething()
{
return $this->OtherClass->getSomething(1, 'Arg2');
}
}
class OtherExampleClass
{
public function getSomething()
{
return "Something";
}
}
class ExampleClassTests extends \Enhance\TestFixture
{
public function setUp()
{
}
public function tearDown()
{
}
public function verifyWithAMock()
{
$mock = \Enhance\MockFactory::createMock('OtherExampleClass');
$mock->addExpectation(
\Enhance\Expect::method('getSomething')
->with(1, 'Arg2')
->returns('Something')
->times(1)
);
$target = new ExampleClass($mock);
$result = $target->doSomething();
\Enhance\Assert::areIdentical("Something", $result);
$mock->verifyExpectations();
}
}
\Enhance\Core::runTests();
look at my constructor for ExampleClass.
Because enhance-php's site example injects the $mock object by calling new ExampleClass($mock), I am forced to change my ExampleClass constructor to handle a $mock as an input parameter.
Do I have to handle this for all classes that I want to subject to unit testing with the framework?
Thanks.
This:
function __construct()
{
$this->OtherClass = new OtherExampleClass;
}
Should be:
function __construct($otherClass)
{
$this->OtherClass = $otherClass;
}
Your mock is never injected at this point in your test:
$target = new ExampleClass($mock);
One thing I would recommend no matter what testing framework you are using is type-hinting against the expected class, or interface.
<?php
class ExampleClass
{
private $OtherClass; // OtherClass instance
public function __construct(OtherClass $OtherClass=null)
{
// ...
}
}
I'm no di expert, but I don't see the problem in letting each class call new if an instance isn't provided for a particular dependency. You could also of course take the approach where you use setter methods to configure dependencies.
<?php
class class ExampleClass
{
private $OtherClass; // OtherClass instance
public function setOtherClass(OtherClass $OtherClass)
{
$this->OtherClass = $OtherClass;
}
}
It is lame that the ExampleClass in the sample code doesn't even define the doSomething method from the ExampleDependencyClassTests, but if I understand correctly it looks like Enhance PHP is not forcing you to take a particular style of dependency injection. You can write the test class however you want, so for example if you took the setter method approach I mentioned above, you could change the example mock code to
<?php
class ExampleDependencyClassTests extends \Enhance\TestFixture
{
public function verifyWithAMock()
{
$mock = \Enhance\MockFactory::createMock('ExampleDependencyClass');
$mock->addExpectation(
\Enhance\Expect::method('getSomething')
->with(1, 'Arg2')
->returns('Something')
->times(1)
);
$target = new ExampleClass();
$target->setExampleDependencyClass($mock);
$result = $target->doSomething();
$mock->verifyExpectations();
}
}
Of course it would probly make sense to make the appropriate revisions to the ExampleClass!
<?php
class ExampleClass
{
private $ExampleDependencyClass;
public function addTwoNumbers($a, $b)
{
return $a + $b;
}
public function setExampleDependencyClass(
ExampleDependencyClass $ExampleDependecyClass
) {
$this->ExampleDependecyClass = $ExampleDependecyClass;
}
public function doSomething($someArg)
{
return 'Something';
}
}
I've worked with PHPUnit quite a bit, and honestly you'll have to face the same challenges with Mocks there. My 2 cents, try to model your tests without Mocks if possible ;)
There is a tutorial on NetTuts titled Testing Your PHP Codebase With Enhance PHP, which will definitely help you to get started.
And there is a Quick Start Guide on Enhance PHP.

How do you add abstract class library in the Codeigniter framework?

I have the following code in file called AbstractClass.php in the libraries folder
abstract class AbstractClass {
abstract protected doSomething ();
}
class ConcreteClass extends AbstractClass {
public function doSomething () {};
}
When I try to load the AbstractClass from controllers as follows:
$this->load->library('AbstractClass');
I get Unable to load the requested class: AbstractClass error.
What am I doing wrong? Should I just include the file rather than loading it?
Thanks
Well obviously you cannot load an abstract class directly as this goes against the point of an abstract class.
You can put an abstract class in a file along with another library, but that is a bit pointless and goes against the "one class one file" standard that CI (and all good standards) suggest.
You can include this file with an include() in your library files, or set up an __autoload() function to do it for you. Best place for an __autoload() is the bottom of config.php.
I use abstract classes with CodeIgniter libraries because I have common methods which I want all inherited classes to use which are meaningless on their own. I don't know if what I'm about to suggest is best practise. I suspect it's not, but I personally find it useful. Here's how I do it:
Create a new classes folder in the CodeIgniter application folder.
Add this folder to the path. (I usually do this in the controller.)
if (!strstr(get_include_path(), APPPATH . 'classes')) {
ini_set('include_path', get_include_path() . ':' . APPPATH . 'classes');
}
Create the abstract classes, or other classes, in the classes folder.
Create an extended CodeIgniter library:
require_once('an_abstract_class.php');
class concrete_library extends an_abstract_class {
Use the library as normal:
$this->load->library('concrete_library');
That should do the trick. I hope this is useful.
Ok. I know this is WAY late, but I'm sure many people are having questions about this.
This is actually a limitation of the core Loader class, as it attempts to instantiate each of the items defined by the first parameter. As we all know, Abstract Classes by their very definition are Abstract and CANNOT be instantiated. So how do we get around this?
But most importantly: How do we get around this while conforming to the CodeIgniter Standards?
Since I've only just started using CodeIgniter, I can't really say for certain how Core Extensions were handled in the past. However, in the most recent version, the CodeIgniter framework will allow you to extend and override its core classes by Prefixing the filename with your defined Subclass Prefix (in most cases "MY_") followed by the name of the file you plan on extending.
*/application/core/MY_Loader.php*
<?php
if(!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Loader extends CI_Loader{
public function __construct(){
parent::__construct();
}
}
?>
Chances are good that if you know how to utilize Abstract Classes, you know what this does. Basically, this Class now inherits all properties and methods of the original CI_Loader class. The code snippet above is technically an exact duplicate of the original Loader Class, but the most important thing here is that now this class will take over all load methods rather than the original.
Now all we need to do is provide the loader class with the means to know if it's loading and instantiating a concrete class, or if it's simply including an abstract class.
There are two methods that handle the loading of any Libraries:
Method 1) public function library
Method 2) protected function _ci_load_class
Method 1 handles the processing of all parameters passed to it by iterating through itself if the first parameter is an array, makes sure the data provided is clean, and prevents any actions from being taken if certain criteria aren't met.
Method 2 handles the actual loading of the necessary assets, error handling, etc.
We can override the behavior of methods 1 and 2 by redefining them within our new MY_Loader Class. I've done this by creating almost exact replicas of the original methods, but with the addition of a 4th parameter that - when true - will prevent the Loader from instantiating the defined Library Class in the second method. I've also included an additional method public function abstract_library that will allow you to explicitly define the Library as Abstract in shorthand fashion.
The following is the MY_Loader.php class in its entirety. This will not affect any existing calls to the library method.
Hope this helps!
*/application/core/MY_Loader.php*
<?php
if(!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Loader extends CI_Loader{
public function __construct(){
parent::__construct();
}
public function library($library = '', $params = NULL, $object_name = NULL, $is_abstract=false){
if(is_array($library)){
foreach ($library as $class){
$this->library($class, $params);
}
return;
}
if($library == '' OR isset($this->_base_classes[$library])){
return FALSE;
}
if(!is_null($params) && ! is_array($params)){
$params = NULL;
}
$this->_ci_load_class($library, $params, $object_name, $is_abstract);
}
public function abstract_library($library=''){
$this->library($library, NULL , NULL, true);
}
protected function _ci_load_class($class, $params = NULL, $object_name = NULL, $is_abstract=false)
{
$class = str_replace('.php', '', trim($class, '/'));
$subdir = '';
if(($last_slash = strrpos($class, '/')) !== FALSE){
$subdir = substr($class, 0, $last_slash + 1);
$class = substr($class, $last_slash + 1);
}
foreach(array(ucfirst($class), strtolower($class)) as $class){
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php';
if(file_exists($subclass)){
$baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php';
if (!file_exists($baseclass)){
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
if(in_array($subclass, $this->_ci_loaded_files)){
if(!is_null($object_name)){
$CI =& get_instance();
if(!isset($CI->$object_name)){
return $is_abstract ? true : $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
}
}
$is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
}
include_once($baseclass);
include_once($subclass);
$this->_ci_loaded_files[] = $subclass;
return $is_abstract ? true : $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
}
$is_duplicate = FALSE;
foreach ($this->_ci_library_paths as $path){
$filepath = $path.'libraries/'.$subdir.$class.'.php';
if(!file_exists($filepath)){
continue;
}
if(in_array($filepath, $this->_ci_loaded_files)){
if(!is_null($object_name)){
$CI =& get_instance();
if(!isset($CI->$object_name)){
return $is_abstract ? true : $this->_ci_init_class($class, '', $params, $object_name);
}
}
$is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
}
include_once($filepath);
$this->_ci_loaded_files[] = $filepath;
return $is_abstract ? true : $this->_ci_init_class($class, '', $params, $object_name);
}
} // END FOREACH
if($subdir == ''){
$path = strtolower($class).'/'.$class;
return $this->_ci_load_class($path, $params, $is_abstract);
}
if($is_duplicate == FALSE){
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
}
}
?>
Loading an abstract library:
<?php
$this->load->library("My_Abstract_Library", NULL, NULL, true);
/* -- OR -- */
$this->load->abstract_library("My_Abstract_Library");
?>
I haven't seen any examples around the web of Abstract classes with CI so I wanted to confirm that you can have an Abstract library. There are very good reasons which are fundamental to OOP as to why Abstract classes are useful. Fundamentally for me to ensure the child classes follow certain consistencies.
Let me know if you need an example, as you manually have to include the abstract class make sure you only do it once so you don't get issues with redeclaring classes.
Also don't forget if you have a static function or variable in the Abstract class you can still access it directly without having to load the class as in the below
AbstractClass::static_method_you_want_to_call();
I have found a simple way to use an abstract classes in Codeigniter. Just follow these steps. Go to libraries in system folder
Create an abstract class and dont link it with CI just name it with simple word without CI_
Create an another class name it with the same word as you give name to your file. As shown in code below
Extend that class with abstract class.
Then call abstract class function through your CI_class.
System -> libraries -> lib.php
abstract class B
{
public function lib1()
{
echo "This is library 1";
}
public function lib2()
{
echo "This is library 1";
}
}
class CI_lib extends B
{
public function libs(){
$this->lib1();
}
}
Then call that lib from Controller.

Going from the Controller to the View

I am working on creating my own very simple MVC and I am brainstorming ways to go from the controller to the view. Which involves sending variables from a class to just a plain old PHP page.
I am sure that this has been covered before, but I wanted to see what kind of ideas people could come up with.
//this file would be /controller/my_controller.php
class My_Controller{
function someFunction(){
$var = 'Hello World';
//how do we get var to our view file in the document root?
//cool_view.php
}
}
Some kind of hashtable is a good way to do that. Return your variables as association array which will fill all the gaps in your view.
Store your variables as a property in your controller object, then extract them when rendering
class My_Controller {
protected $locals = array();
function index() {
$this->locals['var'] = 'Hello World';
}
protected function render() {
ob_start();
extract($this->locals);
include 'YOUR_VIEW_FILE.php';
return ob_get_clean();
}
}
You can define those magic __get and __set methods to make it prettier
$this->var = 'test';
I'm also developing my own simple MVC and the most simple way to do it is ...
class My_Controller
{
function someFunction() {
$view_vars['name'] = 'John';
$view = new View('template_filename.php', $view_vars);
}
}
View class
class View
{
public function __construct($template, $vars) {
include($template);
}
}
template_filename.php
Hello, <?php echo $vars['name'];?>
I highly recommend you to take a look at PHP Savant http://phpsavant.com/docs/
I'd checkout Zend_View and how it accomplished view rendering.
You can get the source of View and AbstractView on github - unfortunaly I don't find the current repository (in svn) that easy to browse.
Essentially the view variables are contained in a View object (which your controller would have access to), then the template (plain old php document) is rendered inside that object. That method allows the template access to $this.
It would be something like:
<?php
class View
{
public function render()
{
ob_start();
include($this->_viewTemplate); //the included file can now access $this
return ob_get_clean();
}
}
?>
So in your controller:
<?php
class Controller
{
public function someAction()
{
$this->view->something = 'somevalue';
}
}
?>
And your template:
<p><?php echo $this->something;?></p>
In my opinion this pattern allows you much flexibility with the view.
I created my own MVC for the free PHP course I'm conducting for a handful of people wanting to get better at PHP.
By far the best way to do this is to use the Command + Factory pattern.
E.g.
interface ControllerCommand
{
public function execute($action);
}
In each controller:
class UserController implements ControllerCommand
{
public function execute($action)
{
if ($action == 'login')
{
$data['view_file'] = 'views/home.tpl.php';
}
else if ($action == 'edit_profile')
{
$data['view_file'] = 'views/profile.tpl.php';
$data['registration_status'] = $this->editProfile();
}
return $data;
}
}
From your main front controller:
$data = ControllerCommandFactory::execute($action);
if (!is_null($data)) { extract($data); }
/* We know the view_file is safe, since we explicitly set it above. */
require $view_file;
The point is that every Controllercommand class has an execute function and that returns its view and any data for that view.
For the complete MVC, you can access the open source app by emailing me at theodore[at]phpexperts.pro.

Categories