t4web / actioninjections
ZF2 Module wich allows to inject dependencies in controller action
Requires
- php: >=5.4.0
- zendframework/zend-loader: ~2.5.0
- zendframework/zend-modulemanager: ~2.5.0
- zendframework/zend-mvc: ~2.5.0
- zendframework/zend-servicemanager: ~2.5.0
- zendframework/zend-view: ~2.5.0
Requires (Dev)
- phpunit/phpunit: 4.5.*
This package is auto-updated.
Last update: 2024-10-26 18:06:36 UTC
README
Introduction
ZF2 Module wich allows to inject dependencies in controller action for better incapsulate testing and remove controller dependency from ServiceLocator.
Problem: I have simply CRUD Controller, with actions "create", "update", "show", "delete", i have 3 dependency in action "delete": ViewModel, Request, some Service.
class AjaxController extends Zend\Mvc\Controller\AbstractActionController { public function deleteTimesheetAction() { $view = new ViewModel(); if (!$this->getRequest()->isPost()) { return $view; } $timesheetDeleteService = $this->getServiceLocator()->get('Timesheet\Timesheet\Service\Delete'); $timesheetId = $this->getRequest()->getPost()->get('id', 0); if (!$timesheetDeleteService->delete($timesheetId)) { $view->errors = $timesheetDeleteService->getErrors(); } return $view; } //... }
in this case i can't easy test this controller, because:
- I can't mock ViewModel (constructor calling)
- Very difficult create test for this $this->getRequest()->getPost()->get('id', 0);
- Nobody understand dependencies in current controller, because $this->getServiceLocator()->get('SomeService') inside controller - is bad practice
Ok, refactor it..
Problem: I have simply CRUD Controller, with actions "create", "update", "show", "delete", i have 3 dependency in action "delete": ViewModel, Request, some Service. For height testability i add all dependencies in Controller::__constructor().
class AjaxController extends Zend\Mvc\Controller\AbstractActionController { /** * @var BaseFinder */ private $timesheetFinder; /** * @var BaseFinder */ private $calendarFinder; /** * @var CreateInterface */ private $createService; /** * @var UpdateInterface */ private $updateService; /** * @var DeleteInterface */ private $deleteService; /** * @var AjaxViewModel */ private $view; public function __construct( BaseFinder $timesheetFinder, BaseFinder $calendarFinder, CreateInterface $timesheetCreateService, UpdateInterface $timesheetUpdateService, DeleteInterface $timesheetDeleteService, AjaxViewModel $view) { $this->timesheetFinder = $timesheetFinder; $this->calendarFinder = $calendarFinder; $this->createService = $timesheetCreateService; $this->updateService = $timesheetUpdateService; $this->deleteService = $timesheetDeleteService; $this->view = $view; } public function deleteTimesheetAction() { $view = new ViewModel(); if (!$this->getRequest()->isPost()) { return $this->view; } $timesheetId = $this->getRequest()->getPost()->get('id', 0); if (!$this->deleteService->delete($timesheetId)) { $this->view->errors = $this->deleteService->getErrors(); } return $this->view; } // ... }
class AjaxControllerFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $serviceManager = $serviceLocator->getServiceLocator(); return new AjaxController( $serviceManager->get('Timesheet\Timesheet\Service\Finder'), $serviceManager->get('Calendar\Calendar\Service\Finder'), $serviceManager->get('Timesheet\Timesheet\Service\Create'), $serviceManager->get('Timesheet\Timesheet\Service\Update'), $serviceManager->get('Timesheet\Timesheet\Service\Delete'), $serviceManager->get('Timesheet\Controller\ViewModel\AjaxViewModel') ); } }
in this case i can easy test this controller, but:
- I have to big __constructor (almost god object)
- How many mock's i must create for test on method "delete"?
- Nobody understand where i use each dependency in current controller
- I must test ControllerFactory
Solution: use t4web/ActionInjections
Add in your module.config.php section controller_action_injections
'controller_action_injections' => array( 'Timesheet\Controller\User\AjaxController' => array( 'deleteTimesheetAction' => array( 'request', 'Timesheet\Controller\ViewModel\AjaxViewModel', 'Timesheet\Timesheet\Service\Delete', ), ), ),
where request
, Timesheet\Controller\ViewModel\AjaxViewModel
, Timesheet\Timesheet\Service\Delete
your dependecies, and just use it in your controller action
class AjaxController extends Zend\Mvc\Controller\AbstractActionController { public function deleteTimesheetAction(HttpRequest $request, AjaxViewModel $view, DeleteInterface $timesheetDeleteService) { if (!$request->isPost()) { return $view; } $timesheetId = $request->getPost()->get('id', 0); if (!$timesheetDeleteService->delete($timesheetId)) { $view->setErrors($timesheetDeleteService->getErrors()); } return $view; } //... }
and test it:
class AjaxControllerTest extends \PHPUnit_Framework_TestCase { public function testDeleteTimesheetAction_Delete_ReturnView() { $requestMock = $this->getMockBuilder('Zend\Http\PhpEnvironment\Request')->disableOriginalConstructor()->getMock(); $timesheetDeleteServiceMock = $this->getMockBuilder('T4webBase\Domain\Service\Delete')->disableOriginalConstructor()->getMock(); $ajaxViewModel = new AjaxViewModel(); $timesheetId = 1; $parameters = new Parameters(array('id' => $timesheetId)); $requestMock->expects($this->once()) ->method('isPost') ->will($this->returnValue(true)); $requestMock->expects($this->once()) ->method('getPost') ->will($this->returnValue($parameters)); $timesheetDeleteServiceMock->expects($this->once()) ->method('delete') ->with($this->equalTo($timesheetId)) ->will($this->returnValue(true)); $controller = new AjaxController(); /** @var $result AjaxViewModel */ $result = $controller->deleteTimesheetAction($requestMock, $ajaxViewModel, $timesheetDeleteServiceMock); $this->assertEquals($ajaxViewModel, $result); } //... }
very fast, easy, readable, incapsulate unit test.
Requirements
- Zend Framework 2 (latest master)
Installation
Main Setup
By cloning project
Clone this project into your ./vendor/
directory.
With composer
Add this project in your composer.json:
"repositories": [ { "type": "git", "url": "https://github.com/t4web/actioninjections.git" } ], "require": { "t4web/actioninjections": "dev-master" }
Now tell composer to download Authentication by running the command:
$ php composer.phar update
Post installation
Not need enabling it in your application.config.php
file, just extends from T4webActionInjections\Mvc\Controller\AbstractActionController
Testing
Unit test runnig from authentication module directory.
$ phpunit