I try to make unit test of a custom Exception Handler class (located in app/Exceptions/Handler.php), but i don't know how to do it ...
I don't know how to raise an exception with my mock Request.
Is someone can help me ?
Here is the "render" method :
/**
* #param \Illuminate\Http\Request $request
* #param Exception $e
* #return \Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function render($request, Exception $e)
{
// This will replace our 404 response with
// a JSON response.
if ($e instanceof ModelNotFoundException &&
$request->wantsJson()) {
Log::error('render ModelNotFoundException');
return response()->json([
'error' => 'Resource not found'
], 404);
}
if ($e instanceof TokenMismatchException) {
Log::error('render TokenMismatchException');
return new RedirectResponse('login');
}
if ($e instanceof QueryException) {
Log::error('render QueryException', ['exception' => $e]);
return response()->view('errors.500', ['exception' => $e, 'QueryException' => true],500);
}
return parent::render($request, $e);
}
And this is my test case :
/**
* #test
*/
public function renderTokenMismatchException()
{
$request = $this->createMock(Request::class);
$request->expects($this->once())->willThrowException(new TokenMismatchException);
$instance = new Handler($this->createMock(Application::class));
$class = new \ReflectionClass(Handler::class);
$method = $class->getMethod('render');
$method->setAccessible(true);
$expectedInstance = Response::class;
$this->assertInstanceOf($expectedInstance, $method->invokeArgs($instance, [$request, $this->createMock(Exception::class)]));
}
This how i do it :
/**
* #test
*/
public function renderTokenMismatch()
{
$request = $this->createMock(Request::class);
$instance = new Handler($this->createMock(Container::class));
$class = new \ReflectionClass(Handler::class);
$method = $class->getMethod('render');
$method->setAccessible(true);
$expectedInstance = RedirectResponse::class;
$this->assertInstanceOf($expectedInstance, $method->invokeArgs($instance, [$request, $this->createMock(TokenMismatchException::class)]));
}
It's not easier do something like
$request = $this->createMock(Request::class);
$handler = new Handler($this->createMock(Container::class));
$this->assertInstanceOf(
RedirectResponse::class,
$handler->render(
$request,
$this->createMock(TokenMismatchException::class)
)
);
Related
I am using Symfony Doctrine Events to trigger notification after entity status update.
I want it triggered on postUpdate() of existing entity.
I have defined constants of the selected status and want it recognized before message is triggered.
const TRIAL = 'trial';
const PAID = 'paid';
const DELETED = 'deleted';
public function postUpdate(LifecycleEventArgs $args)
{
$this->handle($args, self::TRIAL);
}
/**
* #param $args
* #param $action
*/
private function handle($args, $action)
{
/** #var EntityManagerInterface $entityManager */
$entityManager = $args->getEntityManager();
$uow = $entityManager->getUnitOfWork();
$entity = $args->getObject();
$changes = $uow->getEntityChangeSet($entity);
if ((!$entity instanceof User) || (!array_key_exists("status", $changes))) {
return;
}
$email = $entity->getEmail();
$status = $entity->getStatus();
$msg = null;
if ($action == self::TRIAL) {
$msg = "{$email} launched with status {$status}";
}
if ($action == self::PAID) {
$msg = "{$email} launched with status {$status}";
}
if ($action == self::DELETED) {
$msg = "{$email} launched with status {$status}";
}
try {
$this->msgService->pushToChannel($this->msgChannel, $msg);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
}
Can listener methods receive an changed status argument to display proper message? Can we have multiple arguments so Symfony can distinguish which status to use?
Like:
$this->handle($args, self::TRIAL);
$this->handle($args, self::PAID);
$this->handle($args, self::DELETED);
Try to check the $changes, like that (not tested, but you'll get the idea):
const TRIAL = 'trial';
const PAID = 'paid';
const DELETED = 'deleted';
public function postUpdate(LifecycleEventArgs $args)
{
$this->handle($args);
}
/**
* #param $args
*/
private function handle($args)
{
/** #var EntityManagerInterface $entityManager */
$entityManager = $args->getEntityManager();
$uow = $entityManager->getUnitOfWork();
$entity = $args->getObject();
$changes = $uow->getEntityChangeSet($entity);
if ((!$entity instanceof User) || (!array_key_exists("status", $changes))) {
return;
}
$email = $entity->getEmail();
$msg = null;
// Check if the status has changed
if(!empty($changes["status"])){
// $changes["status"] contain the previous and the new value in an array like that ['previous', 'new']
// So whe check if the new value is one of your statuses
if(in_array($changes["status"][1], [self::TRIAL, self::PAID, self::DELETED])) {
$msg = "{$email} launched with status {$status}";
}
}
try {
$this->msgService->pushToChannel($this->msgChannel, $msg);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
}
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]));
}
I have the following code in a class:
private function makeRequest(DetailedPayload $detailedPayload, $request): void
{
$this->httpClient
->sendAsync($request)
->then(
function (ResponseInterface $response) use ($detailedPayload) {
$this->handleServerResponse($detailedPayload, $response);
},
function (RequestException $exception) use ($detailedPayload) {
$this->logRequestException($detailedPayload, $exception);
}
);
}
An now my handler functions looks like so:
private function handleServerResponse(DetailedPayload $detailedPayload, ResponseInterface $response): void
{
if (200 === $response->getStatusCode()) {
try {
$this->queueDataPublisher->publish($detailedPayload);
} catch (FailedToPublishQueueDataException $exception) {
$this->logPublisherException($detailedPayload, $response);
}
} else {
$this->logNonOkResponse($detailedPayload, $response);
}
}
Now I want to test my class which has the signature:
public function __construct(Client $httpClient, LoggerInterface $logger, QueueDataPublisher $queueDataPublisher)
I can mock all the logger and the publisher class and also can follow the instruction to mock the http request as mentioned on the guzzle documentation found here: http://docs.guzzlephp.org/en/stable/testing.html
My test looks as below:
/**
* #test
*
* #throws \Exception
*/
public function willMakeHttpRequestToServer()
{
$client = new Client(
[
'handler' => HandlerStack::create(
new MockHandler(
[
new Response(200)
]
)
)
]
);
$logger = $this->prophesize(LoggerInterface::class);
$queueDataPublisher = $this->prophesize(QueueDataPublisher::class);
$transportClient = new TransportClient($client, $logger->reveal(), $queueDataPublisher->reveal());
$detailedPayload = (new DetailedPayload())
->setStepId('test_step_id')
->setStageId('test_stage_id')
->setProtocolId('test_protocol_id')
->setPayload('test_payload');
$queueDataPublisher
->publish($detailedPayload)
->shouldBeCalled();
$transportClient->sendPayload($detailedPayload);
}
But I can never get this test to green. Has andbody tried something like this to test the async request.
Any idea on how I can approach to test this implementation.
The test requrns the response telling the expectation to call of a function on publisher failed as so:
No calls have been made that match:
People forgive me for my English,
These last week I had to make a change from this oauth lucadegasperi / oauth2-server-laravel to the passport with the migration to laravel 5.6. However in my authentication the oauth always returns me access denied, my midddleware is like this.
use Closure;
use Illuminate\Http\Request;
use Response;
use Input;
use Hash;
use App\Usuarios;
use Lang;
use Auth;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\ResourceServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
class OAuth2Middleware {
protected $server;
protected $tokens;
public function __construct(ResourceServer $server, TokenRepository $tokens) {
$this->server = $server;
$this->tokens = $tokens;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
if( $this->validateToken( $request) === null ){
return $next($request);
}
}
catch (Exception\InvalidRequestException $e) { }
catch (Exception\AccessDeniedException $e) { }
$http_codigo = 499;
$retorno['tipo'] = 'erro';
$retorno['mensagem'] = Lang::get('webservice.acesso_negado');
return response( json_encode( $retorno ) , $http_codigo);
}
public function validateToken(Request $request, $localCall = false) {
try {
$psr = (new DiactorosFactory)->createRequest($request);
$psr = $this->server->validateAuthenticatedRequest($psr);
$token = $this->tokens->find(
$psr->getAttribute('oauth_access_token_id')
);
$currentDate = new DateTime();
$tokenExpireDate = new DateTime($token->expires_at);
$isAuthenticated = $tokenExpireDate > $currentDate ? true : false;
if($localCall) {
return $isAuthenticated;
}
else {
return json_encode(array('authenticated' => $isAuthenticated));
}
} catch (OAuthServerException $e) {
if($localCall) {
return false;
}
else {
return json_encode(array('error' => 'Algo ocorreu de errado com a autenticaĆ§Ć£o'));
}
}
}
}
but when you run this method validateAuthenticatedRequest() return not authorizate ever ! The request is right, with bearer and others informations ,What could be going wrong based on the posted code? Any help is welcome, I do not know what else to do..
Tks
I want to throw an array as an exception in php, instead of a string. Is it possible to do this if you define your own class that extends the Exception class?
For example throw new CustomException('string', $options = array('params'));
Sure. It will just be up to your error handling code to be aware of, and make use of the array property appropriately. You can define your custom exception class's constructor to take any parameters you want, and then just be sure to call the base class's constructor from within the constructor definition, eg:
class CustomException extends \Exception
{
private $_options;
public function __construct($message,
$code = 0,
Exception $previous = null,
$options = array('params'))
{
parent::__construct($message, $code, $previous);
$this->_options = $options;
}
public function GetOptions() { return $this->_options; }
}
Then, in your calling code...
try
{
// some code that throws new CustomException($msg, $code, $previousException, $optionsArray)
}
catch (CustomException $ex)
{
$options = $ex->GetOptions();
// do something with $options[]...
}
Have a look at the php docs for extending the exception class:
http://php.net/manual/en/language.exceptions.extending.php
I think I'm a bit too late with an answer, but I wanted to share my solution as well. There are probably more people looking for this :)
class JsonEncodedException extends \Exception
{
/**
* Json encodes the message and calls the parent constructor.
*
* #param null $message
* #param int $code
* #param Exception|null $previous
*/
public function __construct($message = null, $code = 0, Exception $previous = null)
{
parent::__construct(json_encode($message), $code, $previous);
}
/**
* Returns the json decoded message.
*
* #param bool $assoc
*
* #return mixed
*/
public function getDecodedMessage($assoc = false)
{
return json_decode($this->getMessage(), $assoc);
}
}
If you don't want to extend Exception, you can encode your array into a string:
try {
throw new Exception(serialize(['msg'=>"Booped up with %d.",'num'=>123]));
} catch (Exception $e) {
$data = unserialize($e->getMessage());
if (is_array($data))
printf($data['msg'],$data['num']);
else
print($e->getMessage());
}
You can also use json_encode/json_decode if you prefer.
Yes, you can. You will need to extend the Exception class and create a __construct() method to do what you want.