Hi guys,
I'm currently working on a project to implement Zend Framework controller and
model testing and specification. I'm looking for opinions on whether this is
feasible - the main problem I have is that directly instantiating objects
within controllers makes it impossible for any testing suite from isolating the
controller action from it's carrying objects. Without requiring some perverse
parameter acrobatics or requiring configuration heavy injectors like Pico or
Phemto a simple solution (I think) is to enable a simplified object loader of a
few dozen lines to be added for use.
This could be added either as a separate class Zend_Factory, or appended to
Zend_Loader (which seems appropriate). I've copied the current sample code and
unit tests below. In use, it would require a simple call of:
Zend_Factory::create('Zend_Mail');
Then a unit test or BDD spec could mock this object result (before the create()
call of course) using:
Zend_Factory::replaceClass('Zend_Mail', new ZMail_Mock);
Here's the suggested source code and unit tests. Any comments or criticisms
most welcome - not sure if it's worth an actual proposal given it's size but
it's of immense value to my ongoing work. If you prefer online viewing it's at:
http://svn.astrumfutura.org/zendframework/trunk/library/Proposed/Zend/Factory.php
Zend/Factory.php:
<?php
require_once 'Zend/Loader.php';
require_once 'Zend/Registry.php';
/**
* Abstract object instantiation with minimal setup and allow for replacement
* of created object return values by preceding unit tests or BDD specifications
*
*/
class Zend_Factory
{
/**
* Registry instance for holding replacement objects
*
* @var Zend_Registry
*/
protected static $_registry = null;
/**
* Create a new object based on the referenced class name and construction
* parameters. If a registered replacement object exists, this will be
* returned instead.
*
* @param object $className
* @param array $constructionParams
* @return object
*/
public static function create($className, array $constructionParams = null)
{
if (!class_exists($className, true)) {
Zend_Loader::loadClass($className);
}
if (isset(self::$_registry->$className)) {
return self::$_registry->$className;
}
if ($constructionParams !== null) {
$refClass = new ReflectionClass($className);
$createdObject = $refClass->newInstanceArgs($constructionParams);
} else {
$createdObject = new $className;
}
return $createdObject;
}
/**
* Replace the return value of any call for an instance of the referenced
* class name with an alternative object.
*
* @param string $className
* @param object $withObject
*/
public static function replaceClass($className, $withObject)
{
if (self::$_registry == null) {
self::$_registry = new Zend_Registry(array(),
ArrayObject::ARRAY_AS_PROPS);
}
self::$_registry->$className = $withObject;
}
/**
* Clear the registry of replacement objects
*
*/
public static function clearRegistry()
{
if(isset(self::$_registry)) {
self::$_registry = null;
}
}
}
Unit Tests: FactoryTest.php
<?php
/**
* @package Zend_Factory
* @subpackage UnitTests
*/
require_once dirname(dirname(__FILE__)) . '/TestHelper.php';
/** Zend_Factory */
require_once 'Zend/Factory.php';
/** PHPUnit_Framework_TestCase */
require_once 'PHPUnit/Framework/TestCase.php';
// pre included class used in tests
require_once 'Zend/Mail.php';
/**
* @package Zend_Factory
* @subpackage UnitTests
*/
class FactoryTest extends PHPUnit_Framework_TestCase
{
public function testFactoryInstantiatesZendObjectAfterIncluded()
{
$mail = Zend_Factory::create('Zend_Mail');
$this->assertTrue($mail instanceof Zend_Mail);
}
public function testFactoryInstantiatesZendObjectBeforeIncluded()
{
$gdata = Zend_Factory::create('Zend_Gdata_Photos_AlbumEntry');
$this->assertTrue($gdata instanceof Zend_Gdata_Photos_AlbumEntry);
}
public function testFactoryThrowsExceptionIfClassDoesNotExist()
{
try {
$mail = Zend_Factory::create('Zend_Foo');
$this->fail('Did not throw an expected Exception on non-existent
class request');
} catch (Zend_Exception $e) {
}
}
public function testFactoryInstantiatesUsingConstructParams()
{
$registry = Zend_Factory::create('Zend_Registry',
array(array('index'=>'something')));
$this->assertEquals('something', $registry['index']);
}
public function testFactoryAllowsObjectReplacementForClasses()
{
Zend_Factory::replaceClass('Zend_Registry', new Factory_Foo);
$class = Zend_Factory::create('Zend_Registry');
$this->assertTrue($class instanceof Factory_Foo);
}
public function
testFactoryChecksOriginalClassExistenceToPreventBlindReplacement()
{
Zend_Factory::replaceClass('Zend_Foo', new Factory_Foo);
try {
$class = Zend_Factory::create('Zend_Foo');
$this->fail('Expected exception since replaced class never existed
which means replacement could create blind errors');
} catch (Zend_Exception $e) {
}
}
public function tearDown()
{
Zend_Factory::clearRegistry();
}
}
class Factory_Foo
{
}
Best regards,
Paddy
Pádraic Brady
http://blog.astrumfutura.com
http://www.patternsforphp.com
OpenID Europe Foundation Member-Subscriber