Unit testing guzzle http promises callbacks - php

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:

Related

How to mock request method in phpunit mockery?

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]));
}

PHPUnit Laravel : How to Unit Test custom Exception Handler?

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)
)
);

Zend Soap server example

I'm trying to setup soap server by following https://framework.zend.com/blog/2017-01-24-zend-soap-server.html tutorial.
My corresponding changes
<?php
namespace Soap\Controller;
use Soap\Model;
use Zend\Soap\AutoDiscover as WsdlAutoDiscover;
use Zend\Soap\Server as SoapServer;
use Zend\Mvc\Controller\AbstractActionController;
class SoapController extends AbstractActionController{
private $env;
public function __construct(Model\Env $env){
$this->env = $env;
}
public function wsdlAction(){
/** #var \Zend\Http\Request $request */
$request = $this->getRequest();
if (!$request->isGet()) {
return $this->prepareClientErrorResponse('GET');
}
$wsdl = new WsdlAutoDiscover();
$wsdl = new WsdlAutoDiscover();
$this->populateServer($wsdl);
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
$response->getHeaders()->addHeaderLine('Content-Type', 'application/wsdl+xml');
$response->setContent($wsdl->toXml());
return $response;
}
private function prepareClientErrorResponse($allowed){
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
$response->setStatusCode(405);
$response->getHeaders()->addHeaderLine('Allow', $allowed);
return $response;
}
private function populateServer($server){
// Expose a class and its methods:
$server->setClass(Model\Products::class);
// Expose an object instance and its methods:
$server->setObject($this->env);
// Expose a function:
$server->addFunction('Soap\Model\Env\ping');
$server->addFunction('Soap\Model\Env\pong');
}
public function serverAction(){
/** #var \Zend\Http\Request $request */
$request = $this->getRequest();
if (!$request->isPost()) {
return $this->prepareClientErrorResponse('POST');
}
// Create the server
$server = new SoapServer(
$this->url()
->fromRoute('soap/wsdl', [], ['force_canonical' => true]),
[
'actor' => $this->url()
->fromRoute('soap/server', [], ['force_canonical' => true]),
]
);
$server->setReturnResponse(true);
$this->populateServer($server);
$soapResponse = $server->handle($request->getContent());
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
// Set the headers and content
$response->getHeaders()->addHeaderLine('Content-Type', 'application/soap+xml');
$response->setContent($soapResponse);
return $response;
}
}
After running the server I'm getting error
Call to undefined method Zend\Soap\AutoDiscover::setObject()
When I checked in source of AutoDiscover there is no setObject, What changes do I do to fix it.
This is the populateServer method from the link you posted:
use Acme\Model;
function populateServer($server, array $env)
{
// Expose a class and its methods:
$server->setClass(Model\Calculator::class);
// Or expose an object instance and its methods.
// However, this only works for Zend\Soap\Server, not AutoDiscover, so
// should not be used here.
// $server->setObject(new Model\Env($env));
// Expose a function:
$server->addFunction('Acme\Model\ping');
}
And the note just below that:
[...] if you want to create logic that can be re-used between the Server and AutoDiscover instances, you must confine your usage to setClass(). If that class requires constructor arguments or other ways of setting instance state, you should vary the logic for creation of the WSDL via AutoDiscover and creation of the server via Server.

How to test a method which takes a callable as argument with PhpUnit and Prophecy

I'm using Guzzle's asynchronous request and have them implemented in a service that I would like to test now.
My method looks like this (pseudo, so if it's not 100% valid, please excuse)
public function getPlayer(string $uiid, array &$player = [])
{
$options['query'] = ['id' => $uiid];
$promise = $this->requestAsync('GET', $this->endpoint, $options);
$promise->then(function (ResponseInterface $response) use (&$player) {
$player = $response->getBody()->getContents();
});
return $players;
}
Now I want to test it, but I don't really know how to mock the callable, because I'm always getting the error
1) tzfrs\PlayerBundle\Tests\Api\Player\PlayerServiceTest::testGetPlayer
Prophecy\Exception\InvalidArgumentException: Expected callable or instance of PromiseInterface, but got object.
This is how I have it implemented currently
/** #var ObjectProphecy|PromiseInterface $response */
$promise = $this->prophesize(PromiseInterface::class);
$promise->then()->will($this->returnCallback(function (ResponseInterface $response) use (&$player){}));
Didn't work. And this
$this->returnCallback(function (ResponseInterface $response) use (&$player){})
didn't work either. Same error. And when simply trying a dummy callback
$promise->then(function(){});
I get the error Error: Call to a member function then() on string, even after ->reveal()ing the promise first. Any ideas?
I had another idea.
Make a dependency that will make what you make now in requestAsync();
And then create it's mock that will return another mock of promise.
class PromiseMock
{
private $response;
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
public function then($callable)
{
$callable($this->response);
}
}
test looks like
public function testGetPlayer()
{
$response = new Response(200, [], "Your test response");
$promiseMock = new PromiseMock($response);
$mockDependency = $this->getMockBuilder('YourDependencyClass')
->getMock()
->expects("requestAsync")->willReturn($promiseMock);
$service = new YouServiceClass($mockDependency);
$service->getPlayer("76245914-d56d-4bac-8419-9e409f43e777");
}
And in your class changes only
$promise = $this->someNameService->requestAsync('GET', $this->endpoint, $options);
I would inject a processor to your class and call it's callable. Check it out, the rest is quite obvious:
public function __construct(Processor $processor) {
$this->processor = $processor;
}
public function getPlayer(string $uiid, array &$player = [])
{
$options['query'] = ['id' => $uiid];
$promise = $this->requestAsync('GET', $this->endpoint, $options);
$promise->then([$this->processor, "processResponse"]);
$player = $this->processor->getPlayer();
return $players;
}
And processor:
class Processor {
private $player;
public function processResponse (ResponseInterface $response) {
$this->player = $response->getBody()->getContents();
}
public function getPlayer() { return $this->player;}
}

Unable to catch a global exception

Monolog's StreamHandler throws a \UnexpectedValueException if there's a problem with the specified log file.
In my code I'm trying to catch \UnexpectedValueException but I've been not able to.
My code is:
<?php
namespace myNamespace;
use Monolog\Logger as Monolog;
use Monolog\Handler\StreamHandler;
class Logger {
private static $instance;
private static function init()
{
if (!self::$instance)
{
$logger = new Monolog(MY_LOG_CHANNEL);
try
{
$logger->pushHandler(new StreamHandler(MY_LOG_PATH . "/" .
MY_LOG_NAME, Monolog::NOTICE));
}
catch (\UnexpectedValueException $e)
{
writeln("Error starting logger" . $e->getMessage());
die;
}
// and so on
It doesn't work, I get this:
Fatal error: Uncaught exception 'UnexpectedValueException' with message 'The stream or
file [somefile.log] could not be opened: failed to open stream: No such file or
directory' in [path to src/Monolog/Handler/StreamHandler.php ]
As I understand it they have escaped to the global namespace so I should be able to pick it up there. Why can't I? I've tried all combinations of namespace \myNamesace\UnexpectedValueException even Monolog\UnexpectedValueException global or local, to no avail.
Clearly I'm missing something, what is it please?
Edit:
In another class I'm doing an if (file_exists($fileName)) /** do file_get_contents() etc */ else Logger::error($filename . "does not exist")
The error is being triggered within Logger::error() when I call self::init()
The error is caused because I have (purposely) munged the log file path, if it's a valid log file path then the code runs fine. Clearly I want to catch that error, hence the try/catch.
The next line in the trace is the line in the code above: $logger->pushHandler(new StreamHandler(MY_LOG_PATH . "/" . MY_LOG_NAME, Monolog::NOTICE));
Interestingly, the only other place I'm catching an exception (this is just scaffolding code at the moment, no business logic yet) is within the /** do file_get_contents() etc */ bit if I purposely mis-spell the filename var, file_get_contents barfs and a standard
catch (Exception $e) works as I'd expect around the file_get_contents()
This is a pretty old question which probably got resolved in the meanwhile. However, for the sake of future visitors, I would like to point out that the exception is thrown when a log entry will be written to the given file, not at construction time.
The way you have the try { } catch () {} setup, the exception is expected at the creation of StreamHandler. However, the real exception happens whenever you try to send to the log via call like $logger->error("Error message") so you should be catching exceptions there.
On another note, I think throwing exceptions from a logging library is one of the silliest things that one could do. Logging should be idempotent and not affect the state of the running application.
In an old silex application, I also ran into this issue. I want to log to a logstash instance, yet it should not break if logstash isn't available.
Luckily, my application used the logger only typed against the Psr\Log\LoggerInterface, so I could write a decorator preventing the exceptions to break the app at one place, instead of adding a try and catch call on every call within the codebase.
It looks like this:
<?php
namespace Dreamlines\DirectBookingForm\ServiceProvider;
use Psr\Log\LoggerInterface;
/**
* DontThrowLoggerDecorator
*
* Monolog will break on info, error, etc. calls
* This decorator wrap the LoggerInterface and ensures that failure to log won't break the app
**/
class DontThrowLoggerDecorator implements LoggerInterface
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* #inheritDoc
*/
public function emergency($message, array $context = array())
{
try {
$this->logger->emergency($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function alert($message, array $context = array())
{
try {
$this->logger->alert($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function critical($message, array $context = array())
{
try {
$this->logger->critical($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function error($message, array $context = array())
{
try {
$this->logger->error($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function warning($message, array $context = array())
{
try {
$this->logger->warning($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function notice($message, array $context = array())
{
try {
$this->logger->notice($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function info($message, array $context = array())
{
try {
$this->logger->info($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function debug($message, array $context = array())
{
try {
$this->logger->debug($message, $context);
} catch (\Exception $e) {
}
}
/**
* #inheritDoc
*/
public function log($level, $message, array $context = array())
{
try {
$this->logger->log($level, $message, $context);
} catch (\Exception $e) {
}
}
}
and I just wrap my logger instance around this once, in my silex application I am doing it like this:
$app['NOT_BREAKING_LOGGER'] = $app->share(
function () use ($app) {
return new DontThrowLoggerDecorator($app['monolog']);
}
);
and I am injecting the NOT_BREAKING_LOGGER in each of my services instead of the monolog one.

Categories