CodeIgniter, PHPUnit and Netbeans

One of my New Year's resolutions is to get into Test Driven Development (TDD), I primarily use CakePHP for all my personal projects and that has great support for tests and everything is built into the framework so I really have no excuse. I'm admittedly at the very early stages of learning about TDD and actual writing tests, there are plenty of resources available and I've been following The Grumpy Programmer for quite a while and he's written a book about PHP testing which will come in useful.

For quite a few work based projects I use the CodeIgniter framework which currently does not have everything included to begin writing tests so this will document what needs to be done to get things working and integrated into Netbeans. I found a great post which documents integrating an older version of CodeIgniter and I've used that to help me along with this. To be honest not much is different but this is acting as a reference for myself so I know how to do it in the future.

Setting Up PHPUnit

Going to go into very little detail here and direct you to the official installation guide, fairly straight forward; I used the PEAR route on my windows machine without issue.

If installed correctly you should be able to run "phpunit --version" from the command line and get some info on the version you've got installed.

C:\Users\James>phpunit --version
PHPUnit 3.6.11 by Sebastian Bergmann.

CodeIgniter Changes

I'm using the 2.1.3 stable release which currently isn't setup for tests. Looking at the develop branch tests are going to be a part of CodeIgniter going foward which is great however you may not want to use the cutting edge version and just want the stable release or you may have quite an old release and want to integrate tests.

Once downloaded create a tests folder in the root and copy & paste the index.php in there and rename it to bootstrap.php. We also need to create a phpunit.xml file which will identify the bootstrap file and ensure the CodeIgniter code is available in our tests. Our file will look like this:

// tests/phpunit.xml
<phpunit bootstrap="bootstrap.php"
	colors="true"
	convertErrorsToExceptions="true"
	convertNoticesToExceptions="true"
	convertWarningsToExceptions="true"
	processIsolation="false"
	stopOnFailure="false"
	syntaxCheck="false"
	verbose="true">
</phpunit>

Update the following variables in the bootstrap.php file so that the system and applications paths are correct from the new tests folder:

$system_path = '../system';
$application_folder = '../application';

We're now going to create a Model to run tests on, it's going to be very simple to illustrate the point and return hard-coded test data:

<?php
// application/models/post.php
class Post extends CI_Model {

    public function getAll() {
		return array(
			array('title'=>'post 1','content'=>'...'),
			array('title'=>'post 2','content'=>'...'),
			array('title'=>'post 3','content'=>'...'),
			array('title'=>'post 4','content'=>'...'),
			array('title'=>'post 5','content'=>'...'),
		);
    }
}

We're now going to create a Test file which will run tests on the Post Model we've just created:

<?php
// tests/PostTest.php
class PostTest extends PHPUnit_Framework_TestCase {
	private $CI;

	public function setUp() {
		$this->CI = &get_instance();
	}

	public function testGetAllPosts() {
		$this->CI->load->model('post');
		$posts = $this->CI->post->getAll();
		$this->assertEquals(5, count($posts));
	}
}

Let's try to run our test from the command line, it wont work just yet as some CodeIgniter files need changing but we can go through through the process. Navigate to the tests folder and run them with these commands on windows:

cd C:\xampplite\htdocs\CodeIgniter_2.1.3\tests
phpunit .

We should get the following error:

Fatal error: Call to a member function item() on a non-object in C:\xampplite\htdocs\CodeIgniter_2.1.3\system\core\Utf8.php on line 47

Open up the system/core/Utf8.php file and goto line 41, the $CFG variable is marked as global and this is causing issues with PHPUnit. Replace that line by loading the class via CodeIgniter like so:

$CFG =& load_class('Config', 'core');

Re-run the test and you'll be greeted with yet another error; this time in the output class. I wouldn't of been able to get this sorted without using the code mentioned at the very beginning and what they've done is setup an output hook in Codeigniter that bypasses the normal output when these tests are being run. To do this we first need to make a small change to the bootstrap file:

//Change the environment on line 21
define('ENVIRONMENT', 'testing');

//Enable hooks in the application/config/config.php file on line 94
$config['enable_hooks'] = TRUE;

Add the following to the application/config/hooks.php file:

// application/config/hooks.php
$hook['display_override'] = array(
	'class' => 'DisplayHook',
	'function' => 'captureOutput',
	'filename' => 'DisplayHook.php',
	'filepath' => 'hooks'
);

Create a DisplayHook.php file in the application/hooks folder and enter the following code:

<?php
// application/hooks/DisplayHook.php
class DisplayHook {
	public function captureOutput() {
		$this->CI =& get_instance();
		$output = $this->CI->output->get_output();
		if (ENVIRONMENT != 'testing') {
			echo $output;
		}
	}
}

This checks the current ENVIRONMENT and only outputs data if we're not in testing, as we've specified this environment in our bootstrap file normal output wont be seen. Go back to the console and run the tests again and if everything has gone smoothly you should see the following:

C:\xampplite\htdocs\CodeIgniter_2.1.3\tests>phpunit .
PHPUnit 3.6.11 by Sebastian Bergmann.

Configuration read from C:\xampplite\htdocs\CodeIgniter_2.1.3\tests\phpunit.xml

.

Time: 0 seconds, Memory: 5.25Mb

OK (1 test, 1 assertion)

Whoop whoop! You've just written and executed your first test. Feel free to punch the air and shout Victory!

We're now going to create a database and use post data from that to run our tests, it's always advised to use a testing database and not your actual database in case things get overwritten. I've used the following to create a new Database, Table and sample data:

CREATE DATABASE `codeigniter_testing`;

USE `codeigniter_testing`;

CREATE TABLE IF NOT EXISTS `posts` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(250) NOT NULL,
  `body` TEXT NOT NULL,
  `published` TINYINT(1) NOT NULL DEFAULT 0,
  `created` DATETIME,
  `modified` DATETIME,
  `status` TINYINT(1) NOT NULL DEFAULT 1,
  PRIMARY KEY (`id`)
);

INSERT INTO `posts` (`id`, `title`, `body`, `published`, `created`) VALUES
(1, 'Test Post 1', 'This is the body of a test post.', 1, NOW());
INSERT INTO `posts` (`id`, `title`, `body`, `published`, `created`) VALUES
(1, 'Test Post 2', 'This is the body of a test post.', 1, NOW());
INSERT INTO `posts` (`id`, `title`, `body`, `published`, `created`) VALUES
(1, 'Test Post 3', 'This is the body of a test post.', 1, NOW());

Create a new database object for this connection in the application/config/database.php file:

// application/config/database.php
$db['testing']['hostname'] = 'localhost';
$db['testing']['username'] = 'INSERT_USERNAME';
$db['testing']['password'] = 'INSERT_PASSWORD';
$db['testing']['database'] = 'codeigniter_testing';
$db['testing']['dbdriver'] = 'mysql';
$db['testing']['dbprefix'] = '';
$db['testing']['pconnect'] = TRUE;
$db['testing']['db_debug'] = TRUE;
$db['testing']['cache_on'] = FALSE;
$db['testing']['cachedir'] = '';
$db['testing']['char_set'] = 'utf8';
$db['testing']['dbcollat'] = 'utf8_general_ci';
$db['testing']['swap_pre'] = '';
$db['testing']['autoinit'] = TRUE;
$db['testing']['stricton'] = FALSE;

Re-write the Post Model so that it's getting actual post data:

<?php
// application/models/post.php
class Post extends CI_Model {

	public function getAll() {
		$query = $this->db->get('posts');
		$posts = array();

		foreach ($query->result_array() as $row) {
			$posts[] = $row;
		}

	return $posts;
	}
}

Now we need to setup the database connection in our test like this:

<?php
// tests/PostTest.php
class PostTest extends PHPUnit_Framework_TestCase {
	private $CI;

	public function setUp() {
		$this->CI = &get_instance();
		$this->CI->load->database('testing');
	}

	public function testGetAllPosts() {
		$this->CI->load->model('post');
		$posts = $this->CI->post->getAll();
		$this->assertEquals(5, count($posts));
	}
}

Run the test again and you should be greeting by a failing test. This is because we have only inserted 3 posts into our database and our test is expecting 5 in the assertEquals test. Change the 5 to 3 and the test will then pass!

C:\xampplite\htdocs\CodeIgniter_2.1.3\tests>phpunit .
PHPUnit 3.6.11 by Sebastian Bergmann.

Configuration read from C:\xampplite\htdocs\CodeIgniter_2.1.3\tests\phpunit.xml

F

Time: 1 second, Memory: 6.25Mb

There was 1 failure:

1) PostTest::testGetAllPosts
Failed asserting that 1 matches expected 5.

C:\xampplite\htdocs\CodeIgniter_2.1.3\tests\PostTest.php:13
C:\xampplite\php\phpunit:46

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Albeit a very simple case we've setup the test to look in the database. There are lots of things you can test for and these are all explained in the official documentation. I've yet to look at everything in detail so those are also on my reading list.

As I'm windows I wanted those fancy green or red notices in my command prompt and to get those I used Ansicon which I found from this article. Just needed to download those files and run the command from the prompt.

Windows command prompt colours

Netbeans Integration

To get Netbeans upto speed I've followed this article in the past to ensure it's configured correctly. No need to go through the Selenium testing at the end if you don't want (that's for another article) but the rest should guide you through the process of getting it setup. It's also useful for setting up Code Coverage and going through more examples of Unit Tests and the ability to generate them automatically from within Netbeans.

Setup a new Project for your CodeIgniter files, right click on the Project and click on "properties", browse for your "tests" folder underneath the "Source Folder" and click ok.

Setting up the Netbeans Test Folder

Right click the PostTest.php file and hit run, Netbeans will try to run your test and you'll get some HTML code in the output box.

Netbeans HTML output when running a test

There's a "The URI you submitted has disallowed characters" error and that's because Netbeans appends extra information to the URL which CodeIgniter doesn't allow. Re-open your bootstrap.php file and add the following under the "CUSTOM CONFIG VALUES" section:

// $assign_to_config['name_of_config_item'] = 'value of config item';
$assign_to_config['uri_protocol'] = 'PATH_INFO';

Another solution would be to allow all characters in the URL but that could cause security concerns so I've gone this route. Re-run the test and everything should be fine with your test passing:

Netbeans passing a CodeIgniter PHPUnit test

Wrapping Up

Hopefully this has been fairly straight forward and I believe I've only just scratched the surface on Test Driven Development however this should be a good starting point for CodeIgniter developers. As I've mentioned this couldn't of been possible without this article so credit is definitely due there.

Another resolution this year is to get to grips with Git & Github and so the source code for this post is available at my public repo CodeIgniterTDD so feel free to check it out. If you have any improvements or changes just fork that project and I'll try and figure out how to merge them in :)

Posted on 11th January 2013
4 years, 4 months, 2 weeks ago

comments powered by Disqus