unit testing legacy code - zend-framework

I'm new to unit testing and trying to unit test the model validation of an old zend application which is using forms.
Inside one of the forms it creates an instance of a second class and I'm struggling to understand how I can mock the dependent object. The form reads as follows :
class Default_Form_Timesheet extends G10_Form {
public function init() {
parent::init();
$this->addElement( 'hidden', 'idTimesheet', array( 'filters' => array ('StringTrim' ), 'required' => false, 'label' => false ) );
$this->addElement('checkbox', 'storyFilter', array('label' => 'Show my stories'));
$user = new Default_Model_User();
$this->addElement('select', 'idUser', array('filters' => array('StringTrim'), 'class' => 'idUser', 'required' => true, 'label' => 'User'));
$this->idUser->addMultiOption("","");
$this->idUser->addMultiOptions($user->fetchDeveloper());
...
......
My problem occurs when the call is made to $user->fetchDeveloper(). I suspect it has something todo with mocking objects and dependency injection but any guidence would be appreciated. My Failing unit test reads as follows...
require_once TEST_PATH . '/ControllerTestCase.php';
class TimesheetValidationTest extends ControllerTestCase {
public $Timesheet;
public $UserStub;
protected function setUp()
{
$this->Timesheet = new Default_Model_Timesheet();
parent::setUp();
}
/**
* #dataProvider timesheetProvider
*/
public function testTimesheetValid( $timesheet ) {
$UserStub = $this->getMock('Default_Model_User', array('fetchDeveloper'));
$UserStub->expects( $this->any() )
->method('fetchDeveloper')
->will( $this->returnValue(array(1 => 'Mickey Mouse')));
$Timesheet = new Default_Model_Timesheet();
$this->assertEquals(true, $Timesheet->isValid( $timesheet ) );
}
My data provider is in a separate file.
It is terminating at the command line with no output and I'm a bit stumped. Any help would be greatly appreciated.

You can't mock the Default_Model_User class in your test for the form. Because your code is instantiating the class internally you are not able to replace it with a mock.
You have a couple of options for testing this code.
You look into what fetchDeveloper is doing and control what it is returning. Either via a mock object that you can inject somewhere (looks unlikely) or by setting some data so that you know what the data will be. This will make your test a little brittle in that it could break when the data you are using changes.
The other option is to refactor the code so that you can pass the mock into your form. You can set a constructor that would allow you to set the Default_Model_User class and then you would be able to mock it with your test as written.
The constructor would like like this:
class Default_Form_Timesheet extends G10_Form {
protected $user;
public function __construct($options = null, Default_Model_User $user = null){
if(is_null($user)) {
$user = new Default_Model_User();
}
$this->user = $user;
parent::__construct($options);
}
Zend Framework allows options to be passed to forms constructor which I am not sure if you use in your code anywhere so this should not break any of your current functionality. When can then pass an optional Default_Model_User again so as to not break your current functionality. You need to set the values for $this->user before calling parent::__construct otherwise Zend will throw an error.
Now your init function will have to change from:
$user = new Default_Model_User();
to
$user = $this->user;
In your test you can now pass in your mock object and it will be used.
public function testTimesheetValid( $timesheet ) {
$UserStub = $this->getMock('Default_Model_User', array('fetchDeveloper'));
$UserStub->expects( $this->any() )
->method('fetchDeveloper')
->will( $this->returnValue(array(1 => 'Mickey Mouse')));
$Timesheet = new Default_Model_Timesheet(null, $UserStub);
$this->assertEquals(true, $Timesheet->isValid( $timesheet ) );
}
Creating a mock doesn't replace the object so that when new is called that your mock object is created. It creates a new object that extends your class that you can now pass around. new is a death to testability.

Related

Deprecated: ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer

I am getting below error message while using service manager.
How can i resolve this via different approach like constuct....
Deprecated: You are retrieving the service locator from within the
class Users\Controller\LoginController. Please be aware that
ServiceLocatorAwareInterface is deprecated and will be removed in
version 3.0, along with the ServiceLocatorAwareInitializer. You will
need to update your class to accept all dependencies at creation,
either via constructor arguments or setters, and use a factory to
perform the injections. in
C:\wamp64\www\ZendSkeletonApplication-master\vendor\zendframework\zend-mvc\src\Controller\AbstractController.php
on line 258
Below code i have added in module.php
public function getServiceConfig() {
return array(
'abstract_factories' => array(),
'aliases' => array(),
'factories' => array(
// FORMS
'LoginForm' => function ($sm) {
$form = new \Users\Form\LoginForm();
$form->setInputFilter($sm->get('LoginFilter'));
return $form;
},
)
)
}
and from login controller, index action i calling below code
$form = $this->getServiceLocator()->get('LoginForm');
$viewModel = new ViewModel(array('form' => $form));
return $viewModel;
Any help is highly appreciated.
Currently i am using Zend framework 2.5.1 Version
In Zend framework 2.3 Version it was working fine.
Update
Now i am using below code in my controller
// Add this property:
private $table;
// Add this constructor:
public function __construct(LoginForm $table) {
$this->table = $table;
}
and in module.php
// FORMS
Model\AlbumTable::class => function ($sm) {
$form = new \Users\Form\LoginForm();
$form->setInputFilter($sm->get('LoginFilter'));
return Model\AlbumTable;
},
But still i am getting below error
Catchable fatal error: Argument 1 passed to
Users\Controller\LoginController::__construct() must be an instance of
Users\Form\LoginForm, none given, called in
C:\wamp64\www\ZendSkeletonApplication-master\vendor\zendframework\zend-servicemanager\src\AbstractPluginManager.php
on line 252 and defined in
C:\wamp64\www\ZendSkeletonApplication-master\module\Users\src\Users\Controller\LoginController.php
on line 22
There was a lot of problem in the use of serviceLocator in ZF2, Zend tech' did a great job by removing the serviceLocatorAware from the framework, and remove the serviceManager from controllers.
Why ?
Just because some entry and experienced developpers used it in an ugly way, and way too much.
From my point of view, the serviceLocator is meant to be used only in factories.
That's why i keep advising other developper to create factories, without using anonymous function.
Here an example of a controller's factory (not the same as service's factories) : https://github.com/Grafikart/BlogMVC/blob/master/ZendFramework2/module/Blog/src/Blog/Factory/PostControllerFactory.php
And its config line https://github.com/Grafikart/BlogMVC/blob/master/ZendFramework2/module/Blog/config/module.config.controllers.php#L8
And here a Service's factory
<?php
namespace Blog\Factory;
use Blog\Service\CategoryService;
use Doctrine\Common\Persistence\ObjectManager;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CategoryServiceFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return CategoryService
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** #var ObjectManager $em */
$em = $serviceLocator->get('orm_em');
return new CategoryService($em);
}
}
You can do a factory for almost all of your component, even form, you just need to declare those as factories in your config like this :
You can replace the key form_elements by :
controllers
service_manager
view_helpers
validators
It will works the same way :
'form_elements' => array(
'factories' => array(
'Application\Item\Form\Fieldset\ProfileFieldset' =>
'Application\Item\Factory\ProfileFieldsetFactory',
),
'invokables' => array(
'EntityForm' => 'Application\Entities\Form\EntityForm',
'PropertyForm' => 'Application\Item\Form\PropertyForm',
'ProfileForm' => 'Application\Item\Form\ProfileForm',
),
'initializers' => array(
'ObjectManagerInitializer' => 'Application\Initializers\ObjectManagerInitializer',
),
),
Your last error means that your controller is not correctly instanciated, you not give the LoginForm instance, maybe because you didn't create a factory ? Is your controller declared as an invokables ?
For an in depth discussion on deprecating the ServiceLocatorAwareInterface, please read this article by Matthew Weier O'Phinney. Basically, you should avoid hidden dependencies in your controllers by simply setter injecting them through factories as mentioned previously by Hooli.

Injecting variables into Forms __construct function Zend 2

I am trying to prepopulate a form's drop down options via data stored statically in the module.config.php in Zend 2 and am running into a problem which entails:
I try to get the Servicemanager in the __construct() function but it is unavailable.
I move the element declarations to another function within the form class so I can pass variables into it but the view controller cannot find the elements.
I currently call the form via a Servicemanager Invokable. How can I pass these arrays into the form's __construct() function?
Here is the code:
class ILLForm extends Form
{
public function __construct($fieldsetName, $campuses, $ILLTypes, $getFromOptions)
{
parent::__construct('create_ill');
$this
->setAttribute('method', 'post')
->setHydrator(new ClassMethodsHydrator(false))
->setInputFilter(new InputFilter())
;
$ill = new ILLFieldset('ill', $campuses, $ILLTypes, $getFromOptions);
$ill->setName('ill')
->setOptions(array(
'use_as_base_fieldset' => true,
));
$captcha = new Element\Captcha('captcha');
$captchaAdapter = new Captcha\Dumb();
$captchaAdapter->setWordlen(5);
$captcha->setCaptcha($captchaAdapter)
->setLabelAttributes(array('class' => 'sq-form-question-title'))
->setAttribute('class', 'sq-form-field required')
->setLabel("* Captcha")
->setAttribute('title', 'Help to prevent SPAM');
$submit = new Element\Submit('submit');
$submit->setAttribute('value', 'Submit ILL')
->setAttribute('class', 'sq-form-submit')
->setAttribute('title', 'Submit ILL');
$this->add($ill)
->add($captcha)
->add($submit);
}
}
The Indexcontroller Factory that calls the Form:
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllers)
{
$allServices = $controllers->getServiceLocator();
$sm = $allServices->get('ServiceManager');
$smConfig = $allServices->get('config');
$campuses = $smConfig['campuses'];
$illCategories = $smConfig['ILLCategories'];
$getFromOptions = $smConfig['getFromOptions'];
$controller = new IndexController();
$controller->setCampuses($campuses);
$controller->setILLCategories($illCategories);
$controller->setGetFromOptions($getFromOptions);
$controller->setILLForm($sm->get('ILL-form'));
$controller->setILLFormFilter($sm->get('ILL-form-filter'));
//$controller->setParams($sm->get('params'));
return $controller;
}
}
and the relevant module.config.php excerpt:
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'invokables' => array(
'ILL-form-filter' => 'ILL\Form\ILLFormFilter',
'ILL-form' => 'ILL\Form\ILLForm',
),
I ended up taking the form out of the service manager invokables in module.config.php (removed line for 'ILL-form') and call it from the indexControllerFactory.php instead
$create_ill = new Form\ILLForm('create_ill', $campuses, $illCategories, $getFromOptions);
$controller->setILLForm($create_ill);
instead of
$controller->setILLForm($sm->get('ILL-form'));

Why does not work short names when I create custom form elements in Zend Framework 2?

I create custom element like here: ZF2Docs: Advanced use of Forms
1.Create CustomElement class in Application/Form/Element/CustomElement.php
2.Add to my Module.php function
public function getFormElementConfig()
{
return array(
'invokables' => array(
'custom' => 'Application\Form\Element\CustomElement',
),
);
}
If I use FQCN it works fine:
$form->add(array(
'type' => 'Application\Form\Element\CustomElement',
'name' => 'myCustomElement'
));
But if I use short name:
$form->add(array(
'type' => 'Custom',
'name' => 'myCustomElement'
));
throws Exception:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create
an instance for Custom
Problem
The error is probably due to how you are instantiating the $form object. If you just use the new Zend\Form\Form expression or something similar the form will not be set up with the correct service locator.
$form = new \Zend\Form\Form;
$form->add(array(
'type' => 'custom',
'name' => 'foobar',
));
Solution
The trick here is to use the FormElementManager service locator to instantiate the form.
// inside a controller action
$form = $this->getServiceLocator()->get('FormElementManager')->get('Form');
$form->add(array(
'type' => 'custom',
'name' => 'foobar',
));
Better yet, define a form() method in your controller as a shortcut to do this for you:
class MyController extends AbstractActionController
{
public function form($name, $options = array())
{
$forms = $this->getServiceLocator()->get('FormElementManager');
return $forms->get($name, $options);
}
public function createAction()
{
$form = $this->form('SomeForm');
// ...
}
}
Explanation
Each form object is attached to a form factory which is in turn attached to a service locator. This service locator is in charge of fetching all the classes used to instantiate new form/element/fieldset objects.
If you instantiate a new form object (all by itself), a blank service locator is instantiated and used to fetch later classes within that form. But each subsequent object is then attached to that same service locator.
The problem here is that getFormElementConfig configures a very specific instance of this service locator. This is the FormElementManager service locator. Once it's configured, all forms pulled from this service locator will then be attached to this service locator and will be used to fetch other elements/fieldsets etc.
Hope this solves your issue.

CakePHP - Using a different model in current model

I am creating a custom validation function in my model in CakePHP. After reading similar questions I have understood that I could be using ClassRegistry::init('Model'); to load a foreign model in my current model. But it doesn't say much more on the syntax and how to actually use it afterwards. This is what I have tried, but nothing "is happening" when I am trying to print the array to see if it contains the right stuff. Basically I want to pull out the User data to use it in my validation.
class Booking extends AppModel {
public $name = 'Booking';
public $validate = array(
'start_time' => array(
'noOptionViolation' => array(
'rule' => 'noOptionViolation',
'allowEmpty' => false
)
),
);
public function noOptionViolation ($start_time) {
$this->User = ClassRegistry::init('User');
$allUsers = $this->User->find('all');
print_r($allUsers);
}
Is this correct syntax? Can I use all the methods of $this->User just like I would in a controller?
You can use import as detailed on this post:
https://stackoverflow.com/a/13140816/1081396
App::import('Model', 'SystemSettings.SystemSetting');
$settings = new SystemSetting();
$mySettings = $settings->getSettings();
In your example it would be like:
App::import('Model', 'Users.User');
$user = new User();
$allUsers = $user->find('all');
print_r($allUsers);
You could better use the import at the beginning of the model.
You could use this too to load Models
$this->loadModel('User');
and access all functions by
$this->User

Unit testing forms that have a hash element in Zend Framework

I'm trying to test valid form data in one of my Zend_Forms however it is failing due to it having a hash element that is generated randomly and I cannot access the generated hash to put it back into the assertion data. E.g.
$form = new MyForm();
$data = array('username'=>'test');
$this->assertTrue($form->isValid($data));
This fails as it doesn't contain the hash element value.
I had same problem when my form had captcha and I wanted to test it. Two possible solutions that I cant think about:
First render the form (hash will be generated then), then take that element, take value and use it to test form.
Just remove hash element for testing.
Thanks singles. Rendering the form before the tests worked perfectly for my problem. I wouldn't be too happy about removing the hash element for testing as:
You'll be adding a certain amount of
pointless code to remove these
elements during testing.
Security features need to be tested too.
Here's a quick example of how I did it:
public function testLoginSetsSession()
{
// must render the form first to generate the CSRF hash
$form = new Form_Login();
$form->render();
$this->request
->setMethod('POST')
->setPost(array(
'email' => 'test#test.co.uk',
'password' => 'password',
'hash' => $form->hash->getValue()
));
$this->dispatch('/login');
$this->assertTrue(Zend_Auth::getInstance()->hasIdentity());
}
I recently found a great way of testing forms with hash elements. This will use a mock object to stub away the hash element and you won't have to worry about it. You won't even have to do a session_start or anything this way. You won't have to 'prerender' the form either.
First create a 'stub' class like so
class My_Form_Element_HashStub extends Zend_Form_Element_Hash
{
public function __construct(){}
}
Then, add the following to the form somewhere.
class MyForm extends Zend_Form{
protected $_hashElement;
public function setHashElement( Zend_Form_Hash_Element $hash )
{
$this->_hashElement = $hash;
return $this;
}
protected function _getHashElement( $name = 'hashElement' )
{
if( !isset( $this->_hashElement )
{
if( isset( $name ) )
{
$element = new Zend_Form_Element_Hash( $name,
array( 'id' => $name ) );
}
else
{
$element = new Zend_Form_Element_Hash( 'hashElement',
array( 'id' => 'hashElement' ) );
}
$this->setHashElement( $element );
return $this->_hashElement;
}
}
/**
*
* In your init method you can now add the hash element like below
*
*
*
*/
public function init()
{
//other code
$this->addElement( $this->_getHashElement( 'myotherhashelementname' );
//other code
}
The set method is there just for testing purposes really. You probably won't use it at all during real use but now in phpunit you can right the following.
class My_Form_LoginTest
extends PHPUnit_Framework_TestCase
{
/**
*
* #var My_Form_Login
*/
protected $_form;
/**
*
* #var PHPUnit_Framework_MockObject_MockObject
*/
protected $_hash;
public function setUp()
{
parent::setUp();
$this->_hash = $this->getMock( 'My_Form_Element_HashStub' );
$this->_form = new My_Form_Login( array(
'action' => '/',
'hashElement' => $this->_hash
}
public function testTrue()
{
//The hash element will now always validate to true
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
//OR if you need it to validate to false
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
}
You HAVE to create your own stub. You can't just call the phpunit method getMockObject because that will directly extend the hash element and the normal hash element does 'evil' stuff in its constructor.
With this method you don't even need to be connected to a database to test your forms! It took me a while to think of this.
If you want, you can push the setHashElement method ( along with the variable and the get method ) into some FormAbstract base class.
REMEMBER, in phpunit you HAVE to pass the hash element during form construction. If you don't, your init method will get called before your stub hash can be set with the set method and you'll end up using the regular hash element. You'll know you're using the regular hash element because you'll probably get some session error if you're NOT connected to a database.
Let me know if you find this helpful or if you use it.