How to mock request method in phpunit mockery? - php

I started using mockery so I have a problem in doing my unit test . I want to test authenticate middleware , I passed one condition for expectsJson so I need one more pattern to return true from expectesJson like below but not success
Authenticate.php
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
AuthenticatTest.php
class AuthenticateTest extends TestCase
{
/**
* A basic unit test example.
*
* #return void
*/
public function testMiddleware()
{
$request = Request::create(config('app.url') . '500', 'GET',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:8000']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$expectedStatusCode = 401;
$this->assertContains("http://",$method->invokeArgs($middleware,[$request]));
}
public function testMiddlewareElse()
{
$this->mock(Request::class, function($mock) {
$mock->shouldReceive("expectsJson")
->once()->andReturn(true);
});
$request = Request::create(config('app.url') . '200', 'POST',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:00']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$this->assertContains("",$method->invokeArgs($middleware,[$request]));
}
}
testMiddlewareElse is failed , How to return true for $request->expectsJson

Here's how you could test a request for the authentication middleware. Assume that you have a route that requires authentication that is managed by UserController#dashboard (or similar):
public function testMiddleware() {
// You could disable the other middleware of the route if you don't want them to run e.g.
// $this->withoutMiddleware([ list of middleware to disable ]);
$mockController = $this->prophecy(UserController::class);
//This is if the middleware passes and the controller method is called, use shouldNotBeCalled if you expect it to fail
$mockController->dashboard(Prophecy::any())->shouldBeCalled();
$this->app->instance(
UserController::class,
$mockController->reveal()
);
$this->json("GET", url()->action("UserController#dashboard"));
}

I found the solution ! I need to pass mock class in invoke params ...;)
public function testMiddlewareElse()
{
$mock = $this->mock(Request::class, function($mock) {
$mock->shouldReceive("expectsJson")
->once()->andReturn(true);
});
$request = Request::create(config('app.url') . '200', 'POST',[],[],[],['REMOTE_ADDR'=>'127.0.0.1:00']);
$middleware = new Authenticate($this->createMock(Factory::class));
$class = new \ReflectionClass(Authenticate::class);
$method = $class->getMethod("redirectTo");
$method->setAccessible(true);
$this->assertContains("",$method->invokeArgs($middleware,[$mock]));
}

Related

Rewrite PHPUnit test after the at() matcher deprecation

I used to have a PHPUnit code like that (probably not a good one as I am not able to rewrite it):
$authorizator->expects($this->at(0))
->method('isAllowed')
->willReturn($hasSuperPrivilege);
if (!$hasSuperPrivilege) {
$authorizator->expects($this->at(1))
->method('isAllowed')
->willReturn($hasStandardPrivilege);
}
How to achieve the same behavior after the deprecation of at() method?
I don't know how many times the method will be called, so probably the willReturnOnConsecutiveCalls way is not the right one?
If the first call returns false, then the isAllowed() method must be called twice, if it returned true, then only once.
Related issue: https://github.com/sebastianbergmann/phpunit/issues/4297
Tested method:
public function canUserdoSomething(Foo $resource, Identity $identity): bool
{
if (
$this->authorizator->isAllowed(
$identity,
$resource,
'superPrivilege'
)
) {
return true;
}
if (
$this->authorizator->isAllowed(
$identity,
$resource,
'standardPrivilege'
)
) {
return $resource->hasSomeProperty();
}
return false;
}
I'd go with a handwritten fake so you don't have to worry about the number of calls. I did it inline in my example for brevity but if you're using the authorizator in multiple test, it makes sense to extract it into a separate class.
/**
* #covers \MyClass
*/
class MyClassTest extends TestCase
{
private $subject;
private $authorizator;
/**
* #testWith ["superPrivilege", false, true]
* ["standardPrivilege", true, true]
* ["standardPrivilege", false, false]
* ["invalidPrivilege", true, false]
*/
public function testCanUserdoSomething($privelege, $resourceProperty, $expected)
{
$identity = new Identity();
$resource = new Foo($resourceProperty);
$this->authorizator->addPrivilege($identity, $resource, $privelege);
$result = $this->subject->canUserdoSomething($resource, $identity);
self::assertEquals($expected, $result);
}
protected function setUp(): void
{
parent::setUp();
$this->authorizator = new class implements Authorizator {
private $privileges = [];
public function addPrivilege(Identity $identity, Foo $resource, string $privilege): void
{
$this->privileges[] = spl_object_id($identity) . spl_object_id($resource) . $privilege;
}
public function isAllowed(Identity $identity, Foo $resource, string $privilege): bool
{
return in_array(spl_object_id($identity) . spl_object_id($resource) . $privilege, $this->privileges);
}
};
$this->subject = new MyClass($this->authorizator);
}
}

Possibility to return child method from parent class method?

As I am pretty new to Laravel I am currently using the queue and have the following issue with a job.
Because the API I am calling can throttle, I need to have that logic for every method I call, so I created a parent (base) class. (not sure if this is the right way, please correct me if wrong here as well)
So I have the JobClass that extends the BaseJobClass, which should handle creation of the API client.
BaseJob
class BaseJob implements ShouldQueue
{
protected function performActionOrThrottle($method, $parameters, Customer $customer, Marketplace $marketplace) {
$client = $this->createClient($customer, $marketplace);
if (!method_exists($client, $method)) {
return $this->fail(new \Exception("Method {$method} does not exist in " . get_class($client)));
}
try {
$result = $client->{$method}($parameters);
} catch (\Exception $exception)
{
echo $exception->getMessage().PHP_EOL;
return $this->release(static::THROTTLE_LIMIT);
}
return $result;
}
}
Job
class Job extends BaseJob
{
CONST THROTTLE_LIMIT = 60;
CONST RETRY_DELAY = 10;
private $customer;
private $requestId;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(Customer $customer, $requestId = null)
{
$this->customer = $customer;
$this->requestId = $requestId;
echo "Updating Inventory for {$customer->name}\n";
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$marketplace = Marketplace::findOrFail(5);
if (!$report = $this->performActionOrThrottle('GetReport', $this->requestId, $this->customer, $marketplace)) {
echo "Report not available, trying again in " . self::RETRY_DELAY;
return $this->release(self::RETRY_DELAY);
}
...
handle Data
...
return;
}
}
In order do end the job, I need to return the handle() method. How can I return the handle method from the parent method without having to check for the return value and implementing to return it? If I would to so, there is no point for having that parent that should contain all the logic I need for several jobs.

Symfony - get access to the method from another file in controller

I am using symfony 3 and trying to get access to the class I declared in
src/AppBundle/Service/ApiEngine.php
namespace AppBundle\Service;
use DateTime;
class ApiEngine {
private $api_handler;
public function __construct($username, bool $repos) {
$client = new \GuzzleHttp\Client();
$request = 'https://api.github.com/users/' . $username;
$request .= ($repos) ? '/repos' : "";
$res = $client->request('GET', $request);
$this->api_handler = json_decode($res->getBody()->getContents(), true);
}
public function getProfileData() {
return [ /*some data*/ ];
}
}
I declared this file in
config/service.yml
service:
*
*
*
api:
class: AppBundle\Service\ApiEngine
arguments: ["#username", "#repos"]
In controller I am trying to use some of the ApiEngine methods like this:
src/AppBundle/Controller/GitApiController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class GitApiController extends Controller {
/**
* #Route("/{username}", name ="gitapi", defaults={"username": "symfony"})
*/
public function gitApiAction($username) {
$api = $this->get('api')->__construct($username, false)->getProfileData();
return $this->render('gitapi/index.html.twig', $api);
} }
But it gives me this error:
(1/1) ServiceNotFoundException The service "api" has a dependency on a
non-existent service "username".
I advise you to change your class into this for example:
private function __construct($username, bool $repos) {
$client = new \GuzzleHttp\Client();
$request = 'https://api.github.com/users/' . $username;
$request .= ($repos) ? '/repos' : "";
$res = $client->request('GET', $request);
$this->api_handler = json_decode($res->getBody()->getContents(), true);
}
public static function createApiEngine($username, bool $repos)
{
return new self($username, $bool);
}
After inside your controller you can do this:
$api = ApiEngine::createApiEngine($username, false);
$api->getProfileData();
Into your controller you need to insert a use for ApiEngine, in this use case you don't need dependency injection so inside your services.yml remove arguments please

Zend Framework 2 how to test forward in controller using phpunit?

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.

How can I get the action name in a Symfony2 controller?

Is there a way to get the name of the action in a Symfony2 controller?
public function createAction(Request $request, $title) {
// Expected result: create
$name = $this->getActionName();
}
useļ¼š
$request->attributes->get('_controller');
// will get yourBundle\Controller\yourController::CreateAction
$params = explode('::',$request->attributes->get('_controller'));
// $params[1] = 'createAction';
$actionName = substr($params[1],0,-6);
// $actionName = 'create';
I found this snippet (here):
$matches = array();
$controller = $this->getRequest()->attributes->get('_controller');
preg_match('/(.*)\\\(.*)Bundle\\\Controller\\\(.*)Controller::(.*)Action/', $controller, $matches);
which seems to be a promising approach. This regexp actually doesn't work. But it won't be hard to fetch the action name by using strstr(). Works!
And returns (see example)
Array
(
[0] => Acme\MyBundle\Controller\MyController::myAction
[1] => Acme
[2] => My
[3] => My
[4] => my
)
If input was Acme\MyBundle\Controller\MyController::myAction.
Now, I am using this with Symfony 2.8, (and Symfony3):
<?php
namespace Company\Bundle\AppBundle\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Request as BaseRequest;
/**
* Request shortcuts.
*/
class Request extends BaseRequest
{
/**
* Extract the action name.
*
* #return string
*/
public function getActionName()
{
$action = $this->get('_controller');
$action = explode('::', $action);
// use this line if you want to remove the trailing "Action" string
//return isset($action[1]) ? preg_replace('/Action$/', '', $action[1]) : false;
return $action[1];
}
/**
* Extract the controller name (only for the master request).
*
* #return string
*/
public function getControllerName()
{
$controller = $this->get('_controller');
$controller = explode('::', $controller);
$controller = explode('\\', $controller[0]);
// use this line if you want to remove the trailing "Controller" string
//return isset($controller[4]) ? preg_replace('/Controller$/', '', $controller[4]) : false;
return isset($controller[4]) ? $controller[4] : false;
}
}
To use this custom request class, you must "use" it in your web/app*.php controllers:
use Company\Bundle\AppBundle\Component\HttpFoundation\Request;
// ...
$request = Request::createFromGlobals();
// ...
Then in your controller:
class AppController extends Controller
{
/**
* #Route("/", name="home_page")
* #Template("")
*
* #return array
*/
public function homePageAction(Request $request)
{
$controllerName = $request->getControllerName();
$actionName = $request->getActionName();
dump($controllerName, $actionName); die();
// ...
}
Will output:
AppController.php on line 27:
"AppController"
AppController.php on line 27:
"homePageAction"
You can also access these functions through the RequestStack service:
class MyService
{
/**
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function myService()
{
$this->controllerName = $this->requestStack->getMasterRequest()->getControllerName();
$this->actionName = $this->requestStack->getMasterRequest()->getActionName();
// ...
}
If you use Controller as a Service than the schema is different:
$request->attributes->get('_controller'); will return "service_id:createAction"
A possible solution for both schemas:
$controller = $request->attributes->get('_controller');
$controller = str_replace('::', ':', $controller);
list($controller, $action) = explode(':', $controller);
In all version of symfony and without $request or container, service or nothing else... , directly in your method
public function myMethod(){
$methodName = __METHOD__;
return $methodName;
}
// return App\Controller\DefaultController::myMethod
public function mySecondMethod(){
$methodName = explode('::', __METHOD__);
return $methodName[1];
}
// return mySecondMethod

Categories