Phpunit testing callback functions when passing them to a stub method - phpunit

Here is the method to test.
public function performPoolRequest(RecreationRequestsCollection $requests): RecreatedPaymentsPoolReading
{
$count_request = count($requests);
if($count_request) {
$pool_recreate_payments = new RecreatedPaymentsPool();
$this->http_client->sendPoolRequest(
$this->generateRequests($requests),
$count_request,
function (ResponseInterface $response, $index) use ($requests, $pool_recreate_payments) {
$this->successHandler($response, $requests, $index, $pool_recreate_payments);
},
function (BadResponseException $reason, $index) use ($requests, $pool_recreate_payments) {
$this->failureHandler($reason, $requests, $index, $pool_recreate_payments);
}
);
return $pool_recreate_payments;
} else {
throw new PoolRequestException('Incorrect amount of requests: ' . $count_request);
}
}
Difficulties arose with this piece of code:
$this->http_client->sendPoolRequest(
$this->generateRequests($requests),
$count_request,
function (ResponseInterface $response, $index) use ($requests, $pool_recreate_payments) {
$this->successHandler($response, $requests, $index, $pool_recreate_payments);
},
function (BadResponseException $reason, $index) use ($requests, $pool_recreate_payments) {
$this->failureHandler($reason, $requests, $index, $pool_recreate_payments);
}
);
I made a mock object $this->http_client
But I don't know how to test methods in the argumets ($this->successHandler, $this->failureHandler, this->generateRequests($requests)) that are sent to the method sendPoolRequest.
All of these methods are protected. I understand how to test them using a reflection object, but I want to know if there is an option to test them within a single test by checking the values in $ pool_recreate_payments.

You have to fake what the http client is doing in some way. That means: actually calling the callback functions. While it might be possible to do that with a PHPUnit mock object, writing own test doubles is often easier.
Here are a few examples to get you started.
public function testAllFailing()
{
$client = new class implements HttpClient {
public function sendPoolRequest(array $requests, int $numRequests, callable $successHandler, callable $errorHandler)
{
foreach ($requests as $index => $request) {
$errorHandler(new BadResponseException(/*...*/), $index);
}
}
};
$myService = new MyService($client);
$result = $myService->performPoolRequest(/*...*/);
self::assertEquals(/*...*/, $result);
}
public function testAllSucceeding()
{
$client = new class implements HttpClient {
public function sendPoolRequest(array $requests, int $numRequests, callable $successHandler, callable $errorHandler)
{
foreach ($requests as $index => $request) {
$successHandler(new Response(/*...*/), $index);
}
}
};
$myService = new MyService($client);
$result = $myService->performPoolRequest(/*...*/);
self::assertEquals(/*...*/, $result);
}
public function testEveryOtherFails()
{
$client = new class implements HttpClient {
public function sendPoolRequest(array $requests, int $numRequests, callable $successHandler, callable $errorHandler)
{
foreach ($requests as $index => $request) {
if ($index % 2 === 0) {
$successHandler(new Response(/*...*/), $index);
} else {
$errorHandler(new BadResponseException(/*...*/), $index);
}
}
}
};
$myService = new MyService($client);
$result = $myService->performPoolRequest(/*...*/);
self::assertEquals(/*...*/, $result);
}

Since Guzzle is used as the http client, I decided to use its capabilities in terms of creating stubs for the response object. Here is a link to the documentation Guzzle documentation. The solution turned out to be simple and allowed us to extensively test the business logic of the response.

Related

Symfony + RabbitMQ + Ratchet

I've set up websockets by this manual:
http://socketo.me/docs/hello-world
And it's work.
But now I need to send messages to clients from php, and I don't know how to do this.
I've found some manuals with rabbitMQ which I use in my project? like this:
https://github.com/ratchetphp/Ratchet/issues/659
but I can't understand how to use it.
Maybe someone knows?
You can create Symfony console command to start RabbitMQ consumer, start WebSocket server and send messages to a client once ones received from queue.
protected function execute(InputInterface $input, OutputInterface $output): int
{
$loop = LoopFactory::create();
$pusher = new MessageHandler();
$queueAsyncClient = new AsyncClient($loop, [
"host" => $_ENV['RABBITMQ_HOST'],
"port" => $_ENV['RABBITMQ_PORT'],
"vhost" => $_ENV['RABBITMQ_VHOST'],
"user" => $_ENV['RABBITMQ_USERNAME'],
"password" => $_ENV['RABBITMQ_PASSWORD'],
]);
$connect = $queueAsyncClient->connect();
// When client has connected, retrieve channel (as promise)
$connect->then(function (AsyncClient $client) {
return $client->channel();
// Then declare the queue and exchange
})->then(function (Channel $channel) {
// These method calls all return promises, so we need to combine them
return \React\Promise\all([
$channel,
// Create the queue we'll be using
$channel->queueDeclare($_ENV['RABBITMQ_QUEUE'], false, true),
// Declare an exchange
$channel->exchangeDeclare($_ENV['RABBITMQ_EXCHANGE'], 'direct', false, true),
// Bind the queue to the exchange
$channel->queueBind($_ENV['RABBITMQ_QUEUE'], $_ENV['RABBITMQ_EXCHANGE']),
]);
// Then, when the exchange is all hooked up, hook up the pusher
})->then(function ($connection) use ($pusher) {
/** #var Channel $channel (see first section of all() promise above) */
$channel = $connection[0];
// On messages, consume them using the pusher
return $channel->consume(
function (Message $message) use ($pusher, $channel) {
$content = json_decode($message->content, true);
$connections = $pusher->getUserConnections($content["uid"]);
foreach ($connections as $connection) {
$connection->send(json_encode($content["event"]));
}
},
$_ENV['RABBITMQ_QUEUE'],
'',
false,
true // Acknowledges messages
);
})->done();
$webSocketServer = new \React\Socket\TcpServer("tcp://0.0.0.0:" . $_ENV['WEBSOCKET_PORT'] . "/ws", $loop);
$wsServer = new WsServer($pusher);
$wsServer->enableKeepAlive($loop, 30);
$ioServer = new IoServer(
new HttpServer($wsServer),
$webSocketServer
);
$loop->run();
}
MessageHandler class (like Pusher from the Github issue) basically it stores all connections and handles connection open/close and message events.
class MessageHandler implements MessageComponentInterface
{
protected $connections;
public function __construct()
{
$this->connections = new SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->connections->attach($conn);
}
public function onMessage(ConnectionInterface $from, $msg)
{
try {
$content = json_decode($msg, true);
if ($content || !isset($content["uid"])) {
$this->connections->rewind();
while ($this->connections->valid()) {
if ($this->connections->current() === $from) {
$this->connections->setInfo($content["uid"]);
}
$this->connections->next();
}
} else {
echo "Invalid message content: " . $msg;
}
} catch (\Throwable $t) {
echo $t->getMessage();
}
}
public function onClose(ConnectionInterface $conn)
{
$this->connections->detach($conn);
}
public function onError(ConnectionInterface $conn, Exception $e)
{
$this->connections->detach($conn);
$conn->close();
}
/**
* #param string|null $uid
* #return ConnectionInterface[]|null
*/
public function getUserConnections($uid)
{
$connections = [];
$this->connections->rewind();
while ($this->connections->valid()) {
if ($this->connections->getInfo() == $uid) {
$connections[] = $this->connections->current();
}
$this->connections->next();
}
return $connections;
}
/**
* #return ConnectionInterface[]|null
*/
public function allConnections()
{
$connections = [];
$this->connections->rewind();
while ($this->connections->valid()) {
$connections[] = $this->connections->current();
$this->connections->next();
}
return $connections;
}
}

Symfony passing request to function to an other controller

I try to give '$_REQUEST['vider']' to an other controller like this :
return $this->forward('TestBundle:Rapport:bo', array('vider' => 'vider'));
//, array ($_REQUEST['vider'] => 'vider) doesn't work too
But in my function Rapport:bo, $_REQUEST['vider'] is null, i give it in the array, where i failed ?
edit :
my Rapport:bo function :
public function boAction(Request $request) {
var_dump($_REQUEST['vider']); // is null
if ( isset($_REQUEST['vider']) ) ) {
var_dump('test');
}
}
Try with:
public function boAction($vider) {
your logic...

phpunit must be traversable or implement interface Iterator

I'm trying to unit test my service, containing a dependency Finder Component of symfony2(http://symfony.com/doc/current/components/finder.html).
I'm getting :
[Exception] Objects returned by Mock_Finder_91776c5c::getIterator()
must be traversable or implement interface Iterator
The service :
public function getFile($fileName, $path = '/')
{
if($this->wrapper == null || $this->connection == null)
throw new LogicException("call method setFTP first");
// get file on ftp server
$this->connection->open();
$downloadComplete = $this->wrapper->get($this->tmpDir . $fileName, $path . $fileName);
$this->connection->close();
if($downloadComplete == false)
return false; // TODO exception ?
// return file downloaded
$this->finder->files()->in(__DIR__);
foreach ($this->finder as $file) {
return $file;
}
return false; // TODO exception ?
}
And the test
class FtpServiceTest extends \PHPUnit_Framework_TestCase
{
protected $connectionMock;
protected $ftpWrapperMock;
protected $finderMock;
protected function setUp()
{
$this->connectionMock = $this->getConnectionMock();
$this->ftpWrapperMock = $this->getFTPWrapperMock();
$this->finderMock = $this->getFinderMock();
}
protected function tearDown()
{
}
private function getFinderMock()
{
return $this->getMockBuilder(Finder::class)
->disableOriginalConstructor()
->getMock('Iterator');
}
private function getConnectionMock()
{
return $this->getMockBuilder(Connection::class)
->disableOriginalConstructor()
->getMock();
}
private function getFTPWrapperMock()
{
return $this->getMockBuilder(FTPWrapper::class)
->disableOriginalConstructor()
->getMock();
}
// tests
public function testMe()
{
// arrange
$host = 'localhost';
$user = 'user';
$password = '1234';
$filesArray = new ArrayObject(array(''));
$service = new FtpService('var/tmp/');
$service->setFTP($this->connectionMock, $this->ftpWrapperMock);
$service->setFinder($this->finderMock);
$this->connectionMock
->expects($this->once())
->method('open');
$this->ftpWrapperMock
->expects($this->once())
->method('get')
->will($this->returnValue(true));
$this->connectionMock
->expects($this->once())
->method('close');
$this->finderMock
->expects($this->once())
->method('files')
->will($this->returnValue($this->finderMock));
$this->finderMock
->expects($this->once())
->method('in')
->will($this->returnValue($filesArray));
// act
$file = $service->getFile('/file.zip');
// assert
$this->assertInstanceOf(SplFileInfo::class, $file);
}
}
The mocked instance of the Finder class needs to implement/mock the method getIterator. The getMock method does not accept arguments so don't pass the string 'Iterator'—change the code as follows:
private function getFinderMock()
{
return $this->getMockBuilder(Finder::class)
->disableOriginalConstructor()
->getMock();
}
And add the Mocked expectation in the test method, for example:
$this->finderMock->expects($this->once())
->method('getIterator')
->willReturn(new \ArrayObject([$this->createMock(SplFileInfo::class)]));
Hope this helps.

Variable No Longer Exists after Creating Subrequest in Silex (Symfony2)

I have this issue where I'm attempting to create a subrequest in Silex and basically forward my parameters to another controllers. Exhibit A is broken below (after attempts to refactor), and Exhibit B, the original version, works:
Exhibit A ($this->app is lost after creating the request):
class EntriesController {
private $app;
private $req;
public function __construct($app, $req) {
$this->app = $app;
$this->req = $req;
}
public function updateAction() {
//...
//$url defined here (eyesore-ingly long, so not shown)
$subRequest = Request::create($url, 'GET', $params, $this->req->cookies->all(), array(), $this->req->server->all());
//$this->app **no longer** exists here
return $this->app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
EntriesController instance is created below:
class AppControllerProvider implements ControllerProviderInterface {
public function connect(Application $app) {
$controllers = $app['controllers_factory'];
//...
$controllers->patch('/edit', function (Request $req) use ($app) {
$entriesCtrl = new \EntriesController($app, $req);
return $entriesCtrl->updateAction();
});
//...
}
Exhibit B (works just fine):
class AppControllerProvider implements ControllerProviderInterface {
public function connect(Application $app) {
$controllers = $app['controllers_factory'];
$controllers->patch('/edit', function (Request $req) use ($app) {
//...
//$url defined here
$subRequest = Request::create($url, 'GET', $params, $this->req->cookies->all(), array(), $this->req->server->all());
return $this->app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
//...
});
I basically just reorganized the logic from Exhibit B's 'PATCH' /edit method body into a controller class, and I passed the Silex Application instance $app to a new instance of the controller class.
The only difference between Exhibit A and Exhibit B as far as I can tell is that you instantiate a controller object in the path method callback. Maybe there is something wrong with how this controller is setup or a namespace issue? Shootin' in the dark here.
I can confirm that in my silex application the following code does not produce a null $app container:
GlobalControllerProvider.php
<?php
namespace Dev\Pub\Provider\Controller;
use Silex\Application;
use Silex\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\Request;
class GlobalControllerProvider implements ControllerProviderInterface
{
public function connect(Application $app)
{
$controllers = $app['controllers_factory'];
$controllers
->get('/', 'Dev\Pub\Controller\GlobalController::indexAction')
->bind('homepage')
;
$controllers
->patch('/edit', function (Request $req) use ($app) {
$entriesCtrl = new \Dev\Pub\Controller\GlobalController();
return $entriesCtrl->updateAction($app, $req);
});
return $controllers;
}
}
GlobalController.php
<?php
namespace Dev\Pub\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class GlobalController
{
public function indexAction(Application $app, Request $request)
{
return new Response($app['twig']->render('index.html.twig'));
}
public function updateAction(Application $app, Request $request)
{
$url = 'http://silex.local/index_dev.php/';
$params = array();
$subRequest = Request::create($url, 'GET', $params, $request->cookies->all(), array(), $request->server->all());
// outputs: 'Silex\Application'
error_log(print_r(get_class($app),1).' '.__FILE__.' '.__LINE__,0);
// outputs: 1
error_log(print_r(is_object($app),1).' '.__FILE__.' '.__LINE__,0);
return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
index.main.js
$(function(){
console.log('index.main.js');
$.ajax({
url: "http://silex.local/index_dev.php/edit",
method: "PATCH"
}).done(function( data ) {
console.log(data);
}).fail(function( data ) {
console.log(data);
});
});

Link own queries symfony2 repository

I would like to factorize some code in my models repositories.
A really basic example
public function getPlayers()
{
$qb = $this->createQueryBuilder('p')
->innerJoin(...) // whatever the request
->where(...)
// I want to factorize this line because a lot of function use it
->andWhere('p.active = true');
return (...);
}
So I create a private function
private function getActivePlayer() {
return $this->andWhere('p.active = true');
}
And I would like to use it like that in any function
$qb = $this->createQueryBuilder('p')
->innerJoin(...)
->where(...)
->getActivePlayer()
But of course I have this error
Attempted to call method "getActivePlayer" on class "Doctrine\ORM\QueryBuilder"
It's possible to achieve this kind of factorization? What will be the syntaxe?
Thanks
You can try something like:
public function getPlayers()
{
$qb = $this->createQueryBuilder('p')
->innerJoin(...)
->where(...);
$qb = $this->getPlayerType($qb);
}
private function getActivePlayer(QueryBuilder $qb)
{
return $qb->andWhere('p.active = true');
}
Using the -> operator in the context of a QueryBuilder object will only call methods within the QueryBuilder class. You must define your own class that extends the QueryBuilder. Something like:
class MyQueryBuilder extends \Doctrine\ORM\QueryBuilder {
public function getActivePlayer() {
return $this->andWhere('p.active = true');
}
}
Then implement that builder instead of the default Query Builder:
$qb = new MyQueryBuilder();
$qb->select('p')
->from(...)
->innerJoin(...)
->where(...)
->getActivePlayer()
// ...
Note that the above code is just simple demonstration to show you what length of effort it takes to achieve exactly what you want to do - in actual practice, you'll need to do more than this.
You are best off starting with a basic QueryBuilder in a particular private method then amending it in your public getters:
private function getPlayerQueryBuilder()
{
$qb = $this->createQueryBuilder('p')
->innerJoin(...) // whatever the request
->where(...)
return $qb;
}
public function getActivePlayers() {
$result = $this->getPlayerQueryBuilder()
->andWhere('p.active = true')
->getQuery()->getResult();
return $result;
}
public function getAllPlayers() {
$result = $this->getPlayerQueryBuilder()
->getQuery()->getResult();
return $result;
}

Resources