Using CodeIgniter migrations with PHPUnit

Even some of the CodeIgniter developers are not especially happy about how Migrations are implemented in the current version. Never the less, if you have a CodeIgniter 2.x code base that you want to write unit tests for, you may want to use them.

In a PHPUnit test class, you can use setUp() and tearDown() methods to prepare, run fixtures, create mock objects or whatever else you need to do. Since testing always should be targeted at a test database, one of the things I do is to run migrations. In the setUp() method, I execute migrations to create tables and insert data, in the tearDown() function, I do the opposite. Something like this:

public function __construct($name = NULL, array $data = array(), $dataName = '')
{
	parent::__construct($name, $data, $dataName);
	$this->CI->load->library('migration');
}

public function setUp()
{
	this->CI->migration->version(3);
}

public function tearDown()
{ 
	$this->CI->migration->version(0);
}

Please note: Calling migrations from the setUp() or tearDown() functions may or may not be a very bright idea. It depends a lot on how you organize your tests. setUp() and tearDown() are called once before/after every individual test function in your test class, so static methods setUpBeforeClass() and tearDownAfterClass() may be a lot better:

class InflowLibTest extends CIUnit_TestCase
{
	static public function setUpBeforeClass()
	{
		$CI =& get_instance(); 
		$CI->load->library('migration');
		// Also, make sure that the test db is up to the correct level:
		$CI->migration->version(0);
		$CI->migration->version(1);
	}

	static public function tearDownAfterClass()
	{
		$CI =& get_instance(); 
		$CI->load->library('migration');
		// Tear it down.
		$CI->migration->version(0);
	}

Anyway, I discovered that in the current version,  CodeIgniter Migrations doesn’t do this vey well. The problem is in system/libraries/Migration.php. Line 160 in my file (version 2.1.3), but line 227 in the current Github version.

// Cannot repeat a migration at different steps
if (in_array($match[1], $migrations))
{
$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
return FALSE;
}

include $f[0];
$class = 'Migration_' . ucfirst($match[1]);

if ( ! class_exists($class))
{
$this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
return FALSE;
}

$f[0] is the variable holding the name of the Migration file to load and execute next. Problem is that that line assumes that the file in question isn’t already loaded. Probably an OK assumption in most cases. But when running in context of PHPUnit the way I do it, both setUp() and tearDown() will be run in the same PHP session. So the second time around, when tearDown() executes, all the migration modules will already be included into the PHP process. Simply changing include into require_once fixes the problem. The resulting code should look like this

// Cannot repeat a migration at different steps
if (in_array($match[1], $migrations))
{
$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
return FALSE;
}

require_once $f[0];
$class = 'Migration_' . ucfirst($match[1]);

if ( ! class_exists($class))
{
$this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
return FALSE;
}

 

Enjoy,

 

/E

 

[tags2products]

Leave a comment

Your email address will not be published. Required fields are marked *