I have found a bunch of examples how to unit test Zend_Controller, but I'm looking for examples on Zend_Rest_Controller Unit Testing. Any help is really appreciated. Thank you!
So, basically your question is how to emulate calling PUT and DELETE in your controller tests?
Since this apparently doesn't work:
$this->request->setMethod('PUT');
You can access both these actions with plain HTTP POST by providing _method parameter.
So to call PUT:
$this->request->setMethod('POST');
$this->dispatch('articles/123?_method=put');
To call DELETE:
$this->request->setMethod('POST');
$this->dispatch('articles/123?_method=delete');
More reading on how to deal with RESTful routing here - http://framework.zend.com/manual/en/zend.controller.router.html#zend.controller.router.routes.rest
/**
* Sample class to test a controller
*/
class ArticleControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public $bootstrap;
public function setUp()
{
// When bootstrap is called it will run function 'appBootstrap'
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp();
}
public function appBootstrap()
{
$this->application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini');
$this->application->bootstrap();
$bootstrap = $this->application->getBootstrap();
$front = $bootstrap->getResource('FrontController');
$front->setParam('bootstrap', $bootstrap);
}
public function tearDown()
{
Zend_Controller_Front::getInstance()->resetInstance();
$this->resetRequest();
$this->resetResponse();
parent::tearDown();
}
public function testIndexAction()
{
$testCases = array(
'/article/',
'/article/id/123/',
'/article/authorId/777/limit/5/',
'/article/commentId/999/startDate/2011-06-01/endDate/2011-06-01/',
);
foreach ($testCases as $url) {
$this->request->setHeader('Content-Type', 'text/json');
$this->dispatch($url);
$this->assertResponseCode(200);
$this->assertModule('default');
$this->assertController('article');
$this->assertAction('get');
$body = json_decode($this->response->getBody(), true);
$this->assertNotEmpty($body);
...
$this->resetRequest();
$this->resetResponse();
}
}
public function testGetAction()
{
// Same as $this->testIndexAction()
}
public function testPostAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('POST'); before dispatch
// Change $this->assertResponseCode(200); to 201 as REST requires
}
public function testPutAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('PUT'); before dispatch
}
public function testDeleteAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('DELETE'); before dispatch
}
}
Related
Using Symfony 4.4 with autowiring activated, I want to instantiate a class using the design-pattern FactoryMethod.
The class instantiated is a service with autowired arguments passed into the constructor.
It work well if the constructor is the same for each type of class to instantiate inside the factory method.
But, each service to instantiate has to autowire some specific service in order to work.
I found that we could use the "setter dependency injection". Articles describing it:
https://symfonycasts.com/screencast/symfony-fundamentals/logger-trait
https://symfony.com/doc/4.4/service_container/injection_types.html#setter-injection
I tried to implement the setter dependency injection but the code inside is never executed.
Considering the articles, we should enter the setters with the PHPDoc "#required" immediately after the __construct method has been called (from what I understood).
It doesn't work with my code (see below).
Is my implementation correct?
Is there a better way of doing it?
My code looks like:
// Controller
/**
*#Route("/my_action/{param}")
*/
public function my_action (ThingManagerFactory $thingManagerFactory, $param)
{
$thingManager = $thingManagerFactory->get($param);
$thingManager->doSomething();
}
// ThingManagerFactory
class ThingManagerFactory
{
private $firstManager;
private $secondManager;
private $thirdManager;
public function __construct(FirstManager $firstManager, SecondManager $secondManager, ThirdManager $thirdManager)
{
$this->firstManager = $firstManager;
$this->secondManager = $secondManager;
$this->thirdManager = $thirdManager;
}
public function get($param): ThingManagerInterface
{
if($param == 1) {
return new Thing1Manager(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
);
} elseif($param == 2) {
return new Thing2Manager(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
);
}
throw new \InvalidArgumentException("...");
}
}
// ThingManagerInterface
interface ThingManagerInterface
{
public function __construct(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
);
public function doSomething();
}
// Thing1Manager
class Thing1Manager implements ThingManagerInterface
{
(...)
private $spec1Manager;
public function __construct(
$this->firstManager,
$this->secondManager,
$this->thirdManager,
)
{
(...)
}
/**
* #required
*/
public function setSpecificManager(Spec1Manager $spec1Manager)
{
// this code is never called
$this->spec1Manager = $spec1Manager;
}
public function doSomething()
{
// we pass here before going into setSpecificManager
(...)
}
}
// Thing2Manager class
// is similar to Thing1Manager with multiple other specific managers.
Thank you for your help.
In order to use the design-pattern Factory Method with Symfony, use the Service Locator to provide autowire outside a Controller.
Refactor the code to the following:
// Controller
/**
*#Route("/my_action/{param}")
*/
public function my_action (ThingManagerFactory $thingManagerFactory, $param)
{
$thingManager = $thingManagerFactory->get($param);
$thingManager->doSomething();
}
// ThingManagerFactory
use App\Locator\ThingLocator;
class ThingManagerFactory
{
private $locator;
public function __construct(ThingLocator $locator)
{
$this->locator = $locator;
}
public function get($param): ThingManagerInterface
{
if($param == 1) {
return $this->locator->get(Thing1Manager::class);
} elseif($param == 2) {
return $this->locator->get(Thing2Manager::class);
}
throw new \InvalidArgumentException("...");
}
}
// ServiceLocatorInterface
interface ServiceLocatorInterface
{
public function get(string $id);
}
// ThingLocator
class ThingLocator implements ServiceLocatorInterface, ServiceSubscriberInterface
{
private $locator;
public function __ construct(ContainerInterface $locator)
{
$this->locator = $locator;
}
public function get(string $id)
{
if (!$this->locator->has($id)) {
throw new \Exception("The entry for the given '$id' identifier was not found.");
}
try {
return $this->locator->get($id);
} catch (ContainerExceptionInterface $e) {
throw new \Exception("Failed to fetch the entry for the given '$id' identifier.");
}
}
public static function getSubscribedServices()
{
return [
Thing1Manager::class,
Thing2Manager::class,
];
}
}
// ThingManagerInterface
interface ThingManagerInterface
{
public function doSomething();
}
// Thing1Manager
class Thing1Manager implements ThingManagerInterface
{
// ...
private $spec1Manager;
public function __construct($firstManager, $secondManager, $thirdManager, $spec1Manager)
{
// ...
}
// This setter is no more needed. This manager can be added to the constructor method.
// **
// * #required
// */
//public function setSpecificManager(Spec1Manager $spec1Manager)
//{
// if not commented, this code would be called thanks to the Service Locator (which is a Symfony Service Container)
// $this->spec1Manager = $spec1Manager;
//}
public function doSomething()
{
// ...
}
}
I'm using php-zts to perform parallel data processing, using symfony 4 and PThreads
I'm great at running multiple threads, but I'm facing a problem, I need each of the threads to be able to work with doctrine
I need to make sure that each thread is able to work with doctrine
I tried to transfer a container instance directly, but it won't work because it can't be sterilized
/console_comand.php
private function gettingStatistics(){
$pool = new \Pool(4, Autoloader::class, ["vendor/autoload.php"]);
$store = new \Threaded();
$class = new Meta();
$pool->submit(new Task($class,$store));
$pool->collect();
$pool->shutdown();
$listQuotes = array();
foreach ($store as $obj){
foreach ($obj->{'response'} as $exchange => $data){
$listQuotes[$exchange] = $data;
}
}
unset($store);
unset($interface);
return $listQuotes;
}
/Autoloader.php
<?php
namespace App\Worker;
class Autoloader extends \Worker
{
protected $loader;
public function __construct($loader)
{
$this->loader = $loader;
}
/* включить автозагрузчик для задач */
public function run()
{
require_once($this->loader);
}
/* переопределить поведение наследования по умолчанию для нового потокового контекста */
public function start(int $options = PTHREADS_INHERIT_ALL)
{
return parent::start(PTHREADS_INHERIT_NONE);
}
}
/Autoloadable.php
<?php
namespace App\Worker;
/* нормальный, автоматически загруженный класс */
class Autoloadable
{
public $response;
public function __construct($greeting)
{
$this->response = $greeting->job();
}
}
/Task.php
<?php
namespace App\Worker;
class Task extends \Threaded
{
protected $greeting;
protected $result;
public function __construct($greeting,\Threaded $store)
{
$this->greeting = $greeting;
$this->result = $store;
}
public function run()
{
$greeting = new Autoloadable($this->greeting);
$this->result[] = $greeting;
}
}
how do I pass the right doctrine to be able to work with it from the job?
there's a very similar question on github but I can't deal with it.
https://github.com/krakjoe/pthreads/issues/369
Have you tried requiring an ObjectManager instance in the __construct of Task (your last code block)?
Have a read of this article
Cannot test it atm, don't have zts setup, but I've used this to great success in other projects.
I would expect you need to do something like:
$pool = new Pool(4);
for ($i = 0; $i < 15; ++$i) {
$pool->submit(new class($objectManager) extends \Threaded
{
private $objectManager;
public function __construct(ObjectManager $objectManager)
{
$this->objectManager= $objectManager;
}
public function run()
{
// obviously replace the contents of this function
$this->objectManager->performTask;
echo 'Job\'s done.' . PHP_EOL;
}
});
}
while ($pool->collect());
$pool->shutdown();
The instantiation of the new anonymous class takes the $objectManager present in your current instance, like /console_comand.php there, and passes it to this new anonymous class to fulfill the __construct requirements.
The linked article does a better job of explaining it than I do, so please give it a read.
Not sure why but Its not even hitting the var_dump() that I have. Lets look at how I have it implemented.
<?php
namespace ImageUploader\Controllers;
class ApplicationController implements \Lib\Controller\BaseController {
....
public function beforeAction($actionName = null, $actionArgs = null){}
public function afterAction($actionName = null, $actionArgs = null){}
public static function __callStatic($name, $args) {
var_dump('hello?'); exit;
if (method_exists($this, $name)) {
$this->beforeAction($name, $args);
$action = call_user_func(array($this, $name), $args);
$this->afterAction($name, $args);
return $action;
}
}
}
As we can see I want to do something before and after an action is called, regardless if you implemented the method or not. But that var_dump is never reached.
This class is extended in:
<?php
namespace ImageUploader\Controllers;
use \Freya\Factory\Pattern;
class DashboardController extends ApplicationController {
public function beforeAction($actionName = null, $actionArgs = null) {
var_dump($actionName, $actionArgs); exit;
}
public static function indexAction($params = null) {
Pattern::create('\Freya\Templates\Builder')->renderView(
'dash/home',
array(
'flash' => new \Freya\Flash\Flash(),
'template' => Pattern::create('\Freya\Templates\Builder')
)
);
}
....
}
Now when I do: DashboardController::indexAction(); it should exit ... unless I am missing something. If that's the case - what is it?
even the var_dump in the before_action(...) that's implemented is never reached (obvi' because of the first one, but if I take out the first one the second is never reach.)
__callStatic is called only when a static method does not exist - as indexAction actually is defined, it is executed without bothering __callStatic(). (Documentation)
An approach to achieve what you are trying to do could be by wrapping your controller inside a decorator:
class ExtendedApplicationController
{
/**
* #var \Lib\Controller\BaseController
*/
protected $controller;
function __construct(\Lib\Controller\BaseController $controller) {
$this->controller = $controller;
}
function __callStatic($name, $args) {
if (method_exists($this->controller, 'beforeAction')) {
call_user_func_array(array($this->controller, 'beforeAction'), $name, $args);
}
call_user_func_array(array($this->controller, $name), $args);
if (method_exists($this->controller, 'afterAction')) {
call_user_func_array(array($this->controller, 'afterAction'), $name, $args);
}
}
}
and then, in your code, you could do:
$controller = new ExtendedApplicationController(new DashboardController());
$controller::indexAction();
I have to warn you that I didn't test this approach while I was writing it, but I hope it gives you an idea!
How can I test a forward in a controller with PHPUnit?
I have two simple modules (A and B), module A call the module B using a forward.
here is a simple code that not work :
ModuleA
class ModuleAController extends AbstractRestfulController
{
protected $em;
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em
$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
return $this->em;
}
public function getList()
{
$data = array('message' => 'passed by module A');
$forward = $this->forward()->dispatch('ModuleB\Controller\ModuleB');
$data['Message'] = $forward->getVariable('Message');
return new JsonModel($data);
}
}
ModuleB
class ModuleBController extends AbstractRestfulController
{
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
}
public function getList()
{
$data = array('Message'=>'passed by module B');
return new JsonModel($data);
}
}
And this is a test code :
class ModuleAControllerTest extends AbstractHttpControllerTestCase
{
protected $controller;
protected $request;
protected $response;
protected $routeMatch;
protected $event;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new ModuleAController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array());
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
public function testModuleAControllerCanBeAccessed()
{
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, 1+99+100);
}
}
And this is the error message :
There was 1 error:
1) ModuleATest\Controller\ModuleAControllerTest::testModuleAControllerCanBeAccessed
Zend\ServiceManager\Exception\ServiceNotCreatedException: An exception was raised while creating "forward"; no instance returned
....
Caused by
Zend\ServiceManager\Exception\ServiceNotCreatedException: Zend\Mvc\Controller\Plugin\Service\ForwardFactory requires that the application service manager has been injected; none found
....
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
Is there any way to make this code work ??Any idea ??
Thank you.
I have not created mock for plugins yet. I don't know how set new plugin to controller. But mock will be like it.
PHPunit test file
public function testControllerWithMock()
{
/* Result from ModuleBController method getList() */
$forwardResult = new JsonModel(array('Message'=>'passed by module B'));
/* Create mock object for forward plugin */
$forwardPluginMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\Forward')
->disableOriginalConstructor()
->getMock();
$forwardPluginMock->expects($this->once())
->method('dispatch') /* Replace method dispatch in forward plugin */
->will($this->returnValue($forwardResult)); /* Dispatch method will return $forwardResult */
/* Need register new plugin (made mock object) */
$controller->setPluginManager(); /* ??? Set new plugin to controller */
I'm thinking how decide it.
Ok, try it.
$controller->getPluginManager()->injectController($forwardPluginMock);
I don't write PHPUnit tests for controllers. Because controllers must return a view and best solution using Selenium for testing view. I usually use PHPUnitSelenium tests for testing it.
I am creating a rest service. I have done all the required methods, what I want to do is some authorization. I have created the table where I store the api-keys, i load them in each method, and it works quite well.
What I need now, is to do some before action that would be called before each method, so i don't have to check if the user is successfully authorized on each method? In simple CI_Controller or in FuelPHP that can be done using public function before, but I dont know how to achieve the same thing in REST_Controller?
Thank you in advance
Here are two controllers. May give you some idea
class MY_Controller extends CI_Controller
{
protected $before_filter = array();
protected $after_filter = array();
private function run_filter($who, $params=array())
{
$filter = $this->{"{$who}_filter"};
if (is_string($filter)) {
$filter = array($filter);
}
if (method_exists($this, "{$who}_filter")) {
$filter[] = "{$who}_filter";
}
foreach ($filter as $method) {
call_user_func_array(array($this, $method), $params);
}
}
public function _remap($method, $parameters)
{
if (method_exists($this, $method))
{
$this->run_filter('before', $parameters);
$return = call_user_func_array(array($this, $method),$parameters);
$this->run_filter('after', $parameters);
}else{
show_404();
}
}
}
class MY_Controller extends CI_Controller
{
public $before_filter = array('check_login');
public $after_filter = array();
private function dashboard()
{
/* other code here */
}
public function check_login()
{
/* Login checking Code here */
}
}