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.
Related
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;
}
}
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.
I have this wrapper:
class APIWrapper {
protected static $clientClass = \Hardcoded_API; // from a library
protected $client;
public function __construct() {
$this->client = new self::$clientClass(array_merge(self::$config, $config));
}
/**
* Does something.
*
* #throws SomeException
*/
public function act() {
$this->client->doSomething();
}
public static function setClientClass($clientClass) {
self::$clientClass = $clientClass;
}
}
Now let's say I am testing functions that make use of this wrapper:
class UnrelatedFunction {
public function callAPI() {
$wrapper = new APIWrapper();
try {
$wrapper->act();
} catch (SomeException $e) {
// ... do other things
}
}
}
class UnrelatedFunctionsTest extends PHPUnit_Framework_TestCase {
public function testUnrelatedFunctionException() {
$unrelatedFunction = new UnrelatedFunction();
$this->setExpectedException('SomeException');
// this call triggers the APIWrapper
$unrelatedFunction->callAPI();
// Some assertions here...
}
}
I need to be able to verify that UnrelatedFunction::callAPI() did its job recovering from the exception. In this case, how can I mock a \Hardcoded_API class, make it throw a SomeException when act() is called, and pass it onto APIWrapper::setClientClass() so that I can test the behaviour?
I want to run simple player inserting test.
My code is something like that:
class Player{
public $id;
public $name;
}
class PlayerDAO{
//db stuff
.....
privatefunction input($player){
$player->id = mysql_real_escape_string($player->id);
$player->name = mysql_real_escape_string($player->name);
return $player;
}
public function insert($player){
$player = $this->input($player);
mysql_query("INSERT INTO player (name) VALUES ('.$player->name.')");
}
public function countPlayers(){
$r = mysql_query("SELECT * FROM player");
return mysql_num_rows($r);
}
}
//Test class
class PlayerTest extends PHPUnit_Framework_TestCase {
public function testInsert(){
$player = new Player();
$player->name = 'Test name';
$count1 = PlayerDAO::countPlayers();
PlayerDAO::insert($player);
$count2 = PlayerDAO::countPlayers();
$this->assertEquals(($count2-$count1), 1);
}
}
If i run test, i get error message: Fatal error: Call to undefined method PlayerTest::input() in ...PlayerTest.php. If i remove method input() from insert() method, the error message is gone.
I've been searching and haven't really found much useful information. I'm looking to use the symfony2 routing component (and also yaml component) with my own personal framework but haven't found any documentation on basic setup. The documentation on the symfony site does a good job of explaining how to use the component but not much in the way of standalone setup.
Can anyone recommend a good place to start that could explain how to setup individual symfony2 components?
I ended up looking at the documentation on the symfony site, as well as the source code on github (Route.php, RouteCollection.php, RouteCompiler.php) and came up with my own basic router.
Just like symfony, I allow 4 arguments to be passed to the route class -> 1) the pattern, 2) the default controller/action, 3) regex requirements for each variable in the pattern, and 4) any options - I haven't coded for this yet as it's not in my requirements.
My main/front controller (index.php) only talks to the static Router class which has access to the other route classes in the lib\route namespace.
Router::Map basically compiles a regex string (along with default controller/action and variable names) that gets passed to the compiledRouteCollection which is used in Router::_Process to match the httpRequest against. From there, if there is a match in the process method then i set the controller/action/args based on the values of the matched compiledRoute default controller/action. If there is not a preg_match then i use the default controller/action/args from the request object. The rest is cake...
I hope someone finds this useful and it's able to save them the time I spent today working on this. Cheers!
index.php
require_once 'config/config.php';
require_once 'lib/autoload.php';
lib\Router::Map(
'users_username_id',
'users/{username}/{id}',
array('controller' => 'testController', 'action' => 'users')
);
lib\Router::Route(new lib\Request());
router.php
namespace lib;
class Router {
protected static $_controller;
protected static $_action;
protected static $_args;
protected static $_compiledRouteCollection;
private static $_instance;
private function __construct() {
self::$_compiledRouteCollection = new \lib\route\CompiledRouteCollection();
}
public static function Singleton() {
if(!isset(self::$_instance)) {
$className = __CLASS__;
self::$_instance = new $className;
}
return self::$_instance;
}
public static function Route(Request $request, $resetProperties = false) {
self::Singleton();
self::_Process($request,$resetProperties);
$className = '\\app\\controllers\\' . self::$_controller;
if(class_exists($className, true)) {
self::$_controller = new $className;
if(is_callable(array(self::$_controller, self::$_action))) {
if(!empty(self::$_args)) {
call_user_func_array(array(self::$_controller, self::$_action), self::$_args);
} else {
call_user_func(array(self::$_controller, self::$_action));
}
return;
}
}
self::Route(new \lib\Request('error'),true);
}
public static function Map($name, $pattern, array $defaults, array $requirements = array(), array $options = array()) {
self::Singleton();
$route = new \lib\route\Route($pattern,$defaults,$requirements,$options);
$compiledRoute = $route->Compile();
self::$_compiledRouteCollection->Add($name,$compiledRoute);
}
private static function _Process(Request $request, $resetProperties = false) {
$routes = array();
$routes = self::$_compiledRouteCollection->routes;
foreach($routes as $route) {
preg_match($route[0]['regex'], $request->GetRequest(), $matches);
if(count($matches)) {
array_shift($matches);
$args = array();
foreach($route[0]['variables'] as $variable) {
$args[$variable] = array_shift($matches);
}
self::$_controller = (!isset(self::$_controller) || $resetProperties) ? $route[0]['defaults']['controller'] : self::$_controller;
self::$_action = (!isset(self::$_action) || $resetProperties) ? $route[0]['defaults']['action'] : self::$_action;
self::$_args = (!isset(self::$_args) || $resetProperties) ? $args : self::$_args;
return;
}
}
self::$_controller = (!isset(self::$_controller) || $resetProperties) ? $request->GetController() : self::$_controller;
self::$_action = (!isset(self::$_action) || $resetProperties) ? $request->GetAction() : self::$_action;
self::$_args = (!isset(self::$_args) || $resetProperties) ? $request->GetArgs() : self::$_args;
}
}
request.php
namespace lib;
class Request {
protected $_controller;
protected $_action;
protected $_args;
protected $_request;
public function __construct($urlPath = null) {
$this->_request = $urlPath !== null ? $urlPath : $_SERVER['REQUEST_URI'];
$parts = explode('/', $urlPath !== null ? $urlPath : $_SERVER['REQUEST_URI']);
$parts = array_filter($parts);
$this->_controller = (($c = array_shift($parts)) ? $c : 'index').'Controller';
$this->_action = ($c = array_shift($parts)) ? $c : 'index';
$this->_args = (isset($parts[0])) ? $parts : array();
}
public function GetRequest() {
return $this->_request;
}
public function GetController() {
return $this->_controller;
}
public function GetAction() {
return $this->_action;
}
public function GetArgs() {
return $this->_args;
}
}
route.php
namespace lib\route;
class Route {
private $_pattern;
private $_defaults;
private $_requirements;
public $_options;
public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array()) {
$this->SetPattern($pattern);
$this->SetDefaults($defaults);
$this->SetRequirements($requirements);
$this->SetOptions($options);
}
public function SetPattern($pattern) {
$this->_pattern = trim($pattern);
if($this->_pattern[0] !== '/' || empty($this->_pattern)) {
$this->_pattern = '/'.$this->_pattern;
}
}
public function GetPattern() {
return $this->_pattern;
}
public function SetDefaults(array $defaults) {
$this->_defaults = $defaults;
}
public function GetDefaults() {
return $this->_defaults;
}
public function SetRequirements(array $requirements) {
$this->_requirements = array();
foreach($requirements as $key => $value) {
$this->_requirements[$key] = $this->_SanitizeRequirement($key,$value);
}
}
public function GetRequirements() {
return $this->_requirements;
}
public function SetOptions(array $options) {
$this->_options = array_merge(
array('compiler_class' => 'lib\\route\\RouteCompiler'),
$options
);
}
public function GetOptions() {
return $this->_options;
}
public function GetOption($name) {
return isset($this->_options[$name]) ? $this->_options[$name] : null;
}
private function _SanitizeRequirement($key, $regex) {
if($regex[0] == '^') {
$regex = substr($regex, 1);
}
if(substr($regex, -1) == '$') {
$regex = substr($regex,0,-1);
}
return $regex;
}
public function Compile() {
$className = $this->GetOption('compiler_class');
$routeCompiler = new $className;
$compiledRoute = array();
$compiledRoute = $routeCompiler->Compile($this);
return $compiledRoute;
}
}
routeCompiler.php
namespace lib\route;
class RouteCompiler {
//'#\/tellme\/users\/[\w\d_]+\/[\w\d_]+#'
public function Compile(Route $route) {
$pattern = $route->GetPattern();
$requirements = $route->GetRequirements();
$options = $route->GetOptions();
$defaults = $route->GetDefaults();
$len = strlen($pattern);
$tokens = array();
$variables = array();
$pos = 0;
$regex = '#';
preg_match_all('#.\{([\w\d_]+)\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
if(count($matches)) {
foreach($matches as $match) {
if($text = substr($pattern, $pos, $match[0][1] - $pos)) {
$regex .= str_replace('/', '\/', $text).'\/';
}
if($var = $match[1][0]) {
if(isset($requirements[$var])) {
$regex .= '('.$requirements[$var].')\/';
} else {
$regex .= '([\w\d_]+)\/';
}
$variables[] = $match[1][0];
}
$pos = $match[0][1] + strlen($match[0][0]);
}
$regex = rtrim($regex,'\/').'#';
} else {
$regex .= str_replace('/', '\/', $pattern).'#';
}
$tokens[] = array(
'regex' => $regex,
'variables' => $variables,
'defaults' => $defaults
);
return $tokens;
}
}
compiledRouteCollection.php
namespace lib\route;
class CompiledRouteCollection {
public $routes;
public function __construct() {
$this->routes = array();
}
public function Add($name, array $route) {
$this->routes[$name] = $route;
}
}