Zur Zeit wird gefiltert nach: ExtBase Kochbuch
Filter zurücksetzen
ExtBase Cookbook #11 - Using ExtBase Controller as Subcontroller
During the past couple of days, we thought about how to use an ExtBase Controller as a sub controller. What we wanted to achieve was using a Controller inside another controller to process a certain action. In our project, we work on a extension rendering lists. If you want to use a list inside your plugin, it would be nice to simply call the list controller inside your plugin controller and make it render some domain objects for example. So here is how we wanted the whole thing to work:
/** * Action that is run, whenever a list of galleries should be displayed * * @return string The rendered index action */ public function indexAction() { $subcontrollerFactory = Tx_PtExtlist_Controller_SubcontrollerFactory::getInstanceByListId('gallery_list'); $subcontroller = $subcontrollerFactory->createSubcontroller('list'); $renderedList = $subcontroller->listAction(); $this->view->assign('galleries_list', $renderedList); }
After a little while we discovered some ugly side-effects that did not make it that easy as we first thought to simply use a controller as sub-controller. Most of the problems reside inside the Dispatcher and the mechanism of how requests are dispatched and what happens, whenever a redirect or forward is triggered. So we came up with the following solution which is not quite finished yet but seems to work fine.
First of all, we do not use the controller class itself as subcontroller but generate a wrapper for the controller that handles all dispatching and gives as the way of calling a controller action described above, no matter whether we have forwards or things like that:
<?php /*************************************************************** * Copyright notice * * (c) 2010 Daniel Lienert <lienert@punkt.de>, Michael Knoll <knoll@punkt.de> * All rights reserved * * * This script is part of the TYPO3 project. The TYPO3 project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ /** * Wrapper class for subcontroller. Handles requests for subcontrollers * and emulates dispatching functionality like forwarding etc. * * @author Michael Knoll <knoll@punkt.de> * @package TYPO3 * @subpackage pt_extlist */ class Tx_PtExtlist_Controller_SubcontrollerWrapper extends Tx_PtExtlist_Controller_AbstractController { /** * Holds a reference of the controller to be used as subcontroller * * @var Tx_PtExtlist_Controller_AbstractController */ protected $subcontroller; /** * Holds an instance of subcontroller factory to create * instances of subcontrollers for forwardings etc. * * @var Tx_PtExtlist_Controller_SubcontrollerFactory */ protected $subcontrollerFactory; /** * Holds an instance of current request that has to be processed by subcontroller * * @var Tx_Extbase_MVC_Web_Request */ protected $request; /** * Holds an instance of current response that is manipulated by subcontroller * * @var Tx_Extbase_MVC_Web_Response */ protected $response; /** * Injector for request * * @param Tx_Extbase_MVC_Web_Request $request */ public function injectRequest(Tx_Extbase_MVC_Web_Request $request) { $this->request = $request; } /** * Injector for response * * @param Tx_Extbase_MVC_Web_Response $response */ public function injectResponse(Tx_Extbase_MVC_Web_Response $response) { $this->response = $response; } /** * Injector for subcontroller * * @param Tx_PtExtlist_Controller_AbstractController $subcontroller */ public function injectSubcontroller (Tx_PtExtlist_Controller_AbstractController $subcontroller) { $this->subcontroller = $subcontroller; } /** * Injector for subcontroller factory * * @param Tx_PtExtlist_Controller_SubcontrollerFactory $subcontrollerFactory */ public function injectSubcontrollerFactory (Tx_PtExtlist_Controller_SubcontrollerFactory $subcontrollerFactory) { $this->subcontrollerFactory = $subcontrollerFactory; } /** * Magic function that handles action-calls on subcontroller wrapper. * Checks whether given method name is a correct action and whether action exists in controller * * @param string $method Method name to be called on subcontroller * @param array $args Arguments delivered for action to be called */ public function __call($method, $args) { throw new Exception ('Given method name is not a correct action name: '. $method . ' 1283351947'); } tx_pttools_assert::isNotEmptyString($matches[1], 1283351949')); $controllerActionname = $matches[1]; throw new Exception('Trying to call action ' . $method . ' on ' . 1283351948'); } return $this->processAction($controllerActionname, $args); } /** * Processes action called on subcontroller * * @param string $controllerActionName * @param array $args * @return string HTML source: the rendered action */ protected function processAction($controllerActionName, $args) { $dispatchLoopCount = 0; // TODO insert arguments into request! // Perhaps this works like that: $this->request->setArguments(); $this->request->setControllerActionName($controllerActionName); while (!$this->request->isDispatched()) { if ($dispatchLoopCount > 0) { $this->subController = $this->subcontrollerFactory-> getPreparedSubController($this->request); } if ($dispatchLoopCount++ > 99) throw new Tx_Extbase_MVC_Exception_InfiniteLoop ('Could not ultimately dispatch the request after ' . $dispatchLoopCount . ' iterations.', 1283351950); try { $this->subcontroller->processRequest ($this->request, $this->response); } catch (Tx_Extbase_MVC_Exception_StopAction $ignoredException) { } } return $this->response->getContent(); } }
The next part is the factory that creates this subcontroller wrapper. What you can see here is rather some kind of a prototype... we have to fix some things. First of all, we have to make this thing generic, at the moment it only fits our needs in the context of the list plugin. But I hope you get the idea...
<?php /*************************************************************** * Copyright notice * * (c) 2010 Daniel Lienert <lienert@punkt.de>, Michael Knoll <knoll@punkt.de> * All rights reserved * * * This script is part of the TYPO3 project. The TYPO3 project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ /** * Factory for creating instances of pt_extlist controllers to be used as subcontrollers * * @author Michael Knoll <knoll@punkt.de> * @author Daniel Lienert <lienert@punkt.de> * @package TYPO3 * @subpackage pt_extlist */ class Tx_PtExtlist_Controller_SubcontrollerFactory extends Tx_Extbase_Dispatcher { /** * Holds associative array of instances listIdentifier => factoryInstance * * @var array */ /** * Holds identifier of list, this factory creates controllers for * * @var string */ protected $listIdentifier; /** * Factory method for subcontrollerFactory * * @param string $listIdentifier * @return Tx_PtExtlist_Controller_SubcontrollerFactory */ tx_pttools_assert::isNotEmptyString($listIdentifier, empty list identifier! 1283254996')); if(self::$instancesArray[$listIdentifier] === NULL) { self::$instancesArray[$listIdentifier] = new Tx_PtExtlist_Controller_SubcontrollerFactory($listIdentifier); } return self::$instancesArray[$listIdentifier]; } /** * Constructor for subcontroller factory * * @param string $listIdentifier Identifier of list, factory should create subcontrollers for */ public function __construct($listIdentifier) { $this->cObj = $GLOBALS['TSFE']->cObj; parent::__construct(); $this->listIdentifier = $listIdentifier; } /** * Creates a controller object for list controller * * // TODO make this generic for all controllers (--> how to get dynamic TS config for plugin?) * * @param unknown_type $config * @return unknown */ "userFunc"=> "tx_extbase_dispatcher->dispatch", "pluginName"=> "pi1", "extensionName"=> "PtExtlist", "controller"=> "List", "action"=> "list", "controller"=> "List", "actions"=> "list,sort" ), "controller"=> "Filterbox", "actions"=> "show,submit,reset" ), "controller"=> "Pager", "actions"=> "show,submit" ), "controller"=> "Bookmarks", "actions"=> "show,edit,update,delete,create" ), ), "settings" => "< plugin.tx_ptextlist.settings", "persistence"=> "< plugin.tx_ptextlist.persistence", "view"=> "< plugin.tx_ptextlist.view", "_LOCAL_LANG"=> "< plugin.tx_ptextlist._LOCAL_LANG" ); $this->initializeConfigurationManagerAndFrameworkConfiguration($configuration); // TODO fake request! try { $requestBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_RequestBuilder'); $request = $requestBuilder->initialize(self::$extbaseFrameworkConfiguration); $request = $requestBuilder->build(); } catch(Exception $e) { /* TODO this is done for being testable in CLI environment! */ $actionNames = $configuration['switchableControllerActions.']['1.']['actions']; $request = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Request'); $request->setPluginName($configuration['pluginName']); $request->setControllerExtensionName($configuration['extensionName']); $request->setControllerName($configuration['controller']); $request->setControllerActionName($actions[0]); $request->setRequestURI('http://fakeuri.com'); $request->setBaseURI('http://fakeuri.com'); $request->setMethod('HTTP'); } // Remind setting list identifier in TS! plugin.tx_ptextlist.settings.listIdentifier = <listIdentifier> self::$extbaseFrameworkConfiguration = t3lib_div::array_merge_recursive_overrule( self::$extbaseFrameworkConfiguration, $config ); // we need to check the above conditions as cObj is not available in Backend. $request->setContentObjectData($this->cObj->data); $request->setIsCached($this->cObj->getUserObjectType() == tslib_cObj::OBJECTTYPE_USER); } $response = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Response'); // Request hash service $requestHashService = t3lib_div::makeInstance('Tx_Extbase_Security_Channel_RequestHashService'); // singleton $requestHashService->verifyRequest($request); $persistenceManager = self::getPersistenceManager(); $subcontroller = $this->getPreparedController($request); $subcontrollerWrapper = new Tx_PtExtlist_Controller_SubcontrollerWrapper(); $subcontrollerWrapper->injectSubcontroller($subcontroller); $subcontrollerWrapper->injectSubcontrollerFactory($this); $subcontrollerWrapper->injectRequest($request); $subcontrollerWrapper->injectResponse($response); return $subcontrollerWrapper; } /** * Returns current subcontroller * * @param Tx_Extbase_MVC_Web_Request $request * @return Tx_Extbase_MVC_Controller_ControllerInterface */ public function getPreparedSubController(Tx_Extbase_MVC_Web_Request $request) { return $this->getPreparedController($request); } }
Please understand this as a request for comment. It would be interesting to gather some ideas on how to make this feature run smooth together with ExtBase as you a have a really powerful utility here. I'm looking forward to your comments!
ExtBase Cookbook #10 - Changing Storage PID for repositories
Today I had the problem of how to change the storage pid for a single query in an ExtBase repository. Thanks to the mailinglist, I got the following solution, which I want to share with you:
// Inside your repository: public function findAllRecordsInPid($pid) { $query = $this->createQuery(); $query->getQuerySettings()->setRespectStoragePage(FALSE); $query->matching($query->equals('pid', $pid)); return $query->execute(); }
Just to make this article a little more complete, I want to add some information that has been given to me on the mailinglist:
Steffen Ritter wrote: as a "read" list, i give a full list with all my folders where records should belong to...
for writing I use the following configuration to ensure that every object is created in the correct folder
Tx_Rsmyext_Domain_Model_Donation {
newRecordStoragePid = 37
}
Sebastian Kurfürst added: one more addition; If you want to *move* records from one sysfolder to another, just give them a "pid" property and modify this property as wanted.
Thanks for all your help! I hope, this might be helpful for someone!
Automatischer Formular-Renderer für ExtBase
Am Rande des Entwicklermeetings haben Daniel und ich über einen Weg nachgedacht, wie man in ExtBase automatisch Formulare für (Domänen-)Objekte rendern könnte. Nach dem Motto "Hands on Code" haben wir nach einigen wenigen Überlegungen mal angefangen, ein bisschen was zu implementieren und dabei folgenden Ansatz gewählt:
Einem ViewHelper wird ein Objekt übergeben. Gibt es für dieses Objekt eine TCA Konfiguration einer zugehörigen Datenbanktabelle, wird diese Information genutzt, um die Properties zu Feldtypen zuzuordnen. Gibt es keinen TCA (nicht-domänen-Objekte im ursprünglichen Sinne), so kann eine Konfiguration manuell übergeben werden, welche z.B. in Typoscript geschrieben werden kann. Diese überschreibt im ersten Fall auch die TCA Konfiguration.
Damit ist es möglich, Formulare für beliebige Objekte zu rendern.
Hier ein paar Code Beispiele, wie das bis jetzt aussieht. Betrachten wir das folgenden Domänen-Modell für einen Kommentar (die getter und setter sind der Übersichtlichkeit halber weggelassen!):
class Tx_PtBooks_Domain_Model_Buch extends Tx_Extbase_DomainObject_AbstractEntity { /** * * @var Tx_PtBooks_Domain_Repository_ExemplarRepository $exemplarRepository */ protected $exemplarRepository; /** * title * @var string * @validate NotEmpty */ protected $title; /** * isbn * @var string * @ validate NotEmpty */ protected $isbn; /** * cover * @var string * @ validate NotEmpty */ protected $cover; /** * auflage * @var string * @ validate NotEmpty */ protected $auflage; /** * erscheinungsjahr * @var string * @ validate NotEmpty */ protected $erscheinungsjahr; /** * abstract * @var string * @ validate NotEmpty */ protected $abstract; /** * sprache * @var Tx_PtBooks_Domain_Model_Sprache */ protected $sprache; /** * kategorie * @var Tx_Extbase_Persistence_ObjectStorage<Tx_PtBooks_Domain_Model_Kategorie> */ protected $kategorien; /** * tag * @var Tx_Extbase_Persistence_ObjectStorage<Tx_PtBooks_Domain_Model_Tag> */ protected $tags; /** * verlag * @var Tx_PtBooks_Domain_Model_Verlag */ protected $verlag; /** * autor * @var Tx_Extbase_Persistence_ObjectStorage<Tx_PtBooks_Domain_Model_Autor> */ protected $autoren; /** * kommentar * @var Tx_Extbase_Persistence_ObjectStorage<Tx_PtBooks_Domain_Model_Kommentar> */ protected $kommentare; }
Für dieses Domänenmodell ist folgende TCA Konfiguration der zugehörigen Datenbanktabelle hinterlegt:
[datum] [type]=input [size]=12 [max]=20 [eval]=datetime,required [checkbox]=0 [default]=0 [title] [type]=input [size]=30 [eval]=trim,required [inhalt] [type]=input [size]=30 [eval]=trim,required [verfasser] [type]=select [foreign_table]=tx_ptbooks_domain_model_person [minitems]=0 [maxitems]=1 [buch] [config] [type]=passthrough
Im Fluid Template haben wir folgende Syntax verwendet, um das Objekt als Formular zu rendern:
<punkt:autoform method="post" controller="Kommentar" action="create" name="newKommentar" object="{newKommentar}">
Hier die bisherige Ausgabe unseres ViewHelpers im Frontend:
An dieser Stelle sei darauf hingewiesen, dass unser Renderer im Moment nur mit Input Feldern umgehen kann, darum werden andere Feldtypen im Formular noch mit einer "Fehlermeldung" gerendert.
Wer jetzt schon den Teufel an die Wand gemalt sieht, weil wir TCA fürs Rendering benutzt haben, dem sei versichert, dass das Ganze eben auch mit einer Typoscript - Konfiguration funktionieren würde oder z.B. mit Annotations im Domänenmodell selber. Hier soll der dargestellte weg nur mal einen Proof of Concept darstellen.
Wir sind bei der Arbeit mit den bisherigen ViewHelpern auch auf ein paar Unschönheiten gestoßen, die ich evtl. in einem späteren Post noch zur Diskussion stellen möchte.
Damit's nicht untergeht, hier noch ein paar Schlagwörter für Daniel und mich als Gedankenstütze: GUI-Formular-Designer, Typoscript Namespaces für Konfiguration per Konvention.
ExtBase Cookbook 8 - Writing Tests for Controllers
I had the chance to get a nice hint from Jochen Rau concerning (Unit-)Tests for controllers and ExtBase. Since my last post on this topic, I changed quite a lot in my tests still having the same idea in mind: Getting a fast overview on what is to change after refactoring model classes etc.
Let's say, we have the following controller:
class Tx_Yag_Controller_GalleryController extends Tx_Yag_Controller_AbstractController { protected $galleryRepository; protected $albumRepository; public function initializeAction() { $this->galleryRepository = t3lib_div::makeInstance('Tx_Yag_Domain_Repository_GalleryRepository'); $this->albumRepository = t3lib_div::makeInstance('Tx_Yag_Domain_Repository_AlbumRepository'); } public function indexAction() { $this->view->assign( 'galleries', $this->galleryRepository->findByPageId(6) ); } /* ... */ }
Let's think about what we
a) have to check to make sure, the action is working
b) want to check to make sure, the view holds the data we want it to hold, after the action is performed
So what we clearly have to check is whether the repository returns a value, that we can find in the view later on and that the view gets assigned these values. Take a look at the following test:
class Tx_Yag_Tests_Controller_testcase extends Tx_Extbase_BaseTestCase { /** * @test */ public function indexActionWorks() { $mockGalleryRepository = $this->getMock( 'Tx_Yag_Domain_Repository_GalleryRepository', $mockGalleryRepository->expects($this->once()) ->method('findByPageId') $mockView = $this->getMock( 'Tx_Fluid_Core_View_TemplateView', $mockView->expects($this->once()) ->method('assign') $mockController = $this->getMock( $this->buildAccessibleProxy('Tx_Yag_Controller_GalleryController'), $mockController->_set('galleryRepository', $mockGalleryRepository); $mockController->_set('view', $mockView); $mockController->indexAction(); } /*...*/ }
Line 7 mocks us a Repository object, meaning that instantiates an object of the given type with an overwritten 'findByPageId' method (take a look at PHPUnit to find out, what the other parameters stand for).
Line 10 overwrites the 'findByPageId' method in the mock object and sets its return values. At the same time it sets an assertion which is fullfilled if the method is called exactly one time, when the test is run.
Line 14 mocks our view. We have the same procedure as above, this time concerning the 'assign' method, as we want to check, whether this method is called when running the test and making sure, that it is called with the given parameters.
Line 21 might look a little "magical" but what we do here is using a method that comes with the "Tx_Extbase_BaseTestCase" class which our testcase extends. This class (contained in the ExtBase Framework) enables us to set protected properties in the corresponding classes. So line 24 sets the mocked gallery repository in the tested class and line 25 the mocked view respectively.
Finally we can run 'indexAction()' of our controller and check whether every thing we want it to do is actually done.
Keeping in mind, that 'indexAction()' only has 1 line of code in the tested controller, this might look like a little overhead. But think about a project with - let's say - 10 controllers, 20 actions each. This makes 200 manually proceeded tests whenever some business logic changes due to refactoring or whatever. This tests enable us to see instantly what we have to change in our controllers, whenever logic changes and thus making refactoring a lot more easier... more refactoring --> cleaner code :-)
Tell me your opinions on that - what do you think about it? Perhaps we could one day build a generator for Unit tests in the ExtBase kickstarter to make writing tests a little less time-consuming.
At the end, I want to show you a little more complex test with some more tricks that took me some hours during a train ride to find out how they work. First comes the action, then comes the test for it:
public function editAction(Tx_Yag_Domain_Model_Gallery $gallery) { $this->checkForAdminRights(); $availableAlbums = $this->albumRepository->findAll(); $selectedAlbums = $gallery->getAlbums(); $this->view->assign('availableAlbums', $availableAlbums); $this->view->assign('selectedAlbums', $selectedAlbums); $this->view->assign('gallery', $gallery); }
/** * @test */ public function editActionWorks() { $mockGallery = $this->getMock( $mockGallery->expects($this->once()) ->method('getAlbums') // Dirty trick, as object is cloned when passed to view via assign. So make // compared object cloned too in order to make assertion working. $clonedMockGallery = clone $mockGallery; $mockAlbumRepository = $this->getMock( 'Tx_Yag_Domain_Repository_AlbumRepository', $mockAlbumRepository->expects($this->once()) ->method('findAll') $mockView = $this->getMock( 'Tx_Fluid_Core_View_TemplateView', // I'm not really satisfied with this, as it does not matter // in which order the functions are called, // as long as they are called... $mockView->expects($this->at(0)) ->method('assign') $mockView->expects($this->at(1)) ->method('assign') $mockView->expects($this->at(2)) ->method('assign') ->with('gallery', $clonedMockGallery); $mockController = $this->getMock( $this->buildAccessibleProxy('Tx_Yag_Controller_GalleryController'), $mockController->_set('view', $mockView); $mockController->_set('albumRepository', $mockAlbumRepository); $mockController->editAction($clonedMockGallery); }
ExtBase - Tests für Controller schreiben
Heute mal ausnahmsweise auf deutsch, weil ich Angst habe, dass ich mich auf Englisch nicht klar genug ausdrücken kann. Mit diesem Post möchte ich einen Vorschlag machen, wie in ExtBase Tests für Controller geschrieben werden könnten.
Nachdem die Controller Actions quasi die Herzstücke der Anwendung sind - also zumindest die Teile der Anwendung in denen es gilt dass alles andere funktionieren sollte - hätte ich gerne eine Möglichkeit nach anstehenden Refactorings etc. zu überprüfen, ob die Actions noch problemlos ausgeführt werden. Dazu möchte ich so viele Klassen wie möglich aus dem ExtBase Framework mocken - diese sollten ja bereits getestet sein - ich möchte nur wissen, ob meine Businesslogik im Controller noch so funktioniert, wie sie darin implementiert ist.
Die Frage ist jetzt: Warum teste ich nicht einfach meine Businesslogik und lass die Controller ungetestet. Nun - würde ich das so machen, könnte ich zwar nach jedem Refactoring in der Business Logik über Tests prüfen, ob mein Zeugs noch tut, was es soll. Spätestens wenn sich aber Schnittstellen in den Klassen ändern und ich die Tests anpassen muss, bedeuten laufende Tests der Businesslogik aber noch lange nicht, dass die Controller Actions noch funktionieren. Ich hätte zwar 100% getestete Business-Logik, die Anwendung selber würde aber nicht mehr laufen. Darum denke ich, dass es sinnvoll ist, Tests zu schreiben, die überprüfen, ob die Controller noch fehlerfrei arbeiten.
Da im Bereich der Controller-Verarbeitung in ExtBase eine Gewisse Abhängigkeitshölle herrscht (nicht bös gemeint :-) ), scheiterte mein erster Versuch gnadenlos, in dem ich versuchte, einfach den ExtBase Dispatcher auf eine gemockte TS-Konfiguration loszulassen. Das größte Problem dabei war, dass der Dispatcher über eine Konstante feststellt, dass er im Backend aufgerufen wird und damit sämtliche Frontendlogik den Bach hinunter geht. Kurzum: Unmöglich.
Sebastian Kurfürst machte dann den Vorschlag, möglichst nur die eigentliche Action zu testen und alles andere zu mocken. Immer noch nicht ganz so einfach, wie zuerst gedacht, aber hier ist ein Vorschlag, den ich zur Diskussion stellen möchte. Fangen wir mit einer Implementierung des Testcases an:
<?php class Tx_Yag_Controller_GalleryController_testcase extends Tx_Extbase_BaseTestCase { /** * Holds TS configuration of yag extension * @var array */ private $configuration; /** * @var Tx_Yag_Tests_Mocks_GalleryControllerMock */ private $galleryController; /** * @var Tx_Extbase_Dispatcher */ private $dispatcher; /** * Sets up environment for testing gallery controller * * @return void * @author Michael Knoll <mimi@kaktusteam.de> */ public function setUp() { // This is needed for some basic initialization! $this->dispatcher = new Tx_Extbase_Dispatcher(); $this->configuration = Tx_Yag_Tests_Mocks_ConfigurationMocks::getBasicConfiguration(); $this->galleryController = t3lib_div::makeInstance('Tx_Yag_Tests_Mocks_GalleryControllerMock'); $this->galleryController->injectMockView(new Tx_Fluid_View_TemplateView()); $this->galleryController->injectMockRepository(new Tx_Yag_Tests_Mocks_GalleryRepositoryMock()); $this->galleryController->injectSettings($this->configuration['settings']); } /** * Tests index action of gallery controller * @test */ public function indexAction() { $this->galleryController->indexAction(); } /** * Tests show action of gallery controller * @test */ public function showAction() { $this->galleryController->showAction(new Tx_Yag_Domain_Model_Gallery()); } } ?>
Wie man sehen kann, musste ich um den eigentlichen Controller zu testen eine erbende Klasse implementieren, welche mir den Zugriff auf ein paar versteckte Eigenschaften ermöglicht. Hier meine Controller-Mock Klasse:
<?php class Tx_Yag_Tests_Mocks_GalleryControllerMock extends Tx_Yag_Controller_GalleryController { public function injectMockView(Tx_Extbase_MVC_View_ViewInterface $view) { $this->view = $view; } public function injectMockRepository( Tx_Extbase_Persistence_RepositoryInterface $mockRepository) { $this->galleryRepository = $mockRepository; } public function getView() { return $this->view; } } ?>
Zuletzt war es noch nötig, eine eigene Autoloader Datei für die Tests hinzuzufügen, da der Extbase Autoload nur im "Classes" Verzeichnis einer Extension nach Klassendefinitionsdateien sucht:
<?php $extensionTestsPath = t3lib_extMgm::extPath('yag') . 'Tests/'; 'tx_yag_tests_mocks_configurationmocks' => $extensionTestsPath . 'Mocks/ConfigurationMocks.php', 'tx_yag_tests_mocks_gallerycontrollermock' => $extensionTestsPath . 'Mocks/GalleryControllerMock.php', 'tx_yag_tests_mocks_galleryrepositorymock' => $extensionTestsPath . 'Mocks/GalleryRepositoryMock.php' ); ?>
Ich würde dieses Konzept des Testens gerne zur Diskussion stellen und freue mich über Rückmeldungen egal welcher Art. Für mich ist testgetriebene Entwicklung ein recht neues Spielfeld und ich bin mir nicht sicher, ob meine Ideen gut sind oder nicht.



