Testing with vsfStream and PHP Unit - php

So I'm currently picking up Unit testing and on my travels I've discovered I also might need to mock the file system as well as database queries and the like.
I've read a recommendation from the PHPUnit documentation that vfsStream is a good library for mocking the file system.
I'm having issues with using it, though.
I don't really understand how to set it up despite following documentation provided in the wiki hosted on GitHub.
I've abstracted file system interaction into it's own class with some aptly named methods. That doesn't appear to be the issue at hand, as when I wasn't mocking the file system it worked as intended.
Here is how I setup the test:
public function setUp()
{
// Setup here
$this->filesystem = new Filesystem;
vfsStreamWrapper::register();
$this->root = vfsStream::setup('test-dir');
}
Here is an example test I written to test for the creation of a directory:
public function testCanCreateDirectory()
{
$dir = vfsStream::url($this->root->path() . '/sub-dir');
$filesystem = $this->filesystem;
$filesystem->makeDirectory($dir);
$this->assertTrue($filesystem->isDirectory($dir));
}
That appears to work, so far so good. However, this next test fails:
public function testCanPutFile()
{
$file = $this->root->url() . '/sub-dir/test.txt';
$contents = "test text";
$filesystem = $this->filesystem;
$filesystem->put($file, $contents);
$this->assertTrue($filesystem->isFile($file));
$this->assertEquals($contents, $filesystem->get($file));
}
As far as I can tell from the documentation this should work, but obviously my reading of it isn't correct and I've gone awry somewhere along the lines.
Any hints as to how I can resolve this issue, as I've been changing lines over and over and cannot figure it out for the life of me.
Thank you in advance!

I appear to have solved this. The manner of the fix is... somewhat unsatisfactory, maybe someone could help explain the reasoning behind this I'd be happy.
public function testCanPutFile()
{
$dir = vfsStream::url($this->root->path()) . '/sub-dir';
$file = $dir . '/test.txt';
$contents = "test text";
$filesystem = $this->filesystem;
$this->assertTrue($filesystem->makeDirectory($dir));
$this->assertTrue($filesystem->isDirectory($dir));
$filesystem->put($file, $contents);
$this->assertTrue($filesystem->isFile($file));
$this->assertEquals($contents, $filesystem->get($file));
}
Is how I fixed the issue. It appears I have to recreate the structure per test method. I don't really understand why I have to do it this way. Maybe I'm missing something here but I can't see what I'm doing wrong and there are a paltry number of decent examples on the internet, with most of them found on the authors GitHub wiki. I do believe I've probably misread the documentation though, so a correction on this issue would also be much appreciated.

Related

How to mock MongoClient in PHPUnit

I'm re-engineering an application, and I've chosen to use TDD to do it. I'm new to TDD and have yet to fall in love with the process. Currently, I've run into a problem that I'm not able to find any clear help on. I might just be overthinking, but would certainly appreciate some help understanding what I'm running into.
The project I am working on persists to MongoDB. I've been working with Mongo for a while now and like it a lot, but the PHP driver doesn't seem to fit into my limited understanding of how (and what) to test with a fake/mock.
Here is a sample class (I haven't checked to see if it will run):
class UserCollection {
protected $_mongo_client; //connection to persistence layer (MongoDB or mock)
public function __construct($mongo_client, $id = NULL) {
$this->_mongo_client = $mongo_client;
}
public function getUserInfo($mid) {
$collection = $this->_mongo_client->vertical->primaryMember;
$user = $collection->findOne(array('memberId' => intval($mid)), array('memberId'=> true, 'name'=>true,'stats' => true));
if($user['memberId']) {
$return['status'] = "Success";
$return['user'] = $user;
} else {
$return['status'] = "Failure";
$return['message'] = "User not found";
}
return $return;
}
}
As I understand it, if I am to create this writing the tests first, I need to create a fake for the DB - and I have been trying to come up with a mock to inject into the constructor. How do I create a mock that handles lines like:
$collection = $this->_mongo_client->vertical->primaryMember;
I'm hoping that I am just overthinking the issue, and there is an easy or better way to do this. At any rate I would appreciate any links, sage advice, or blunt corrections to my thinking.
Another question here addresses this topic as well.
Phactory provides direct support for mocking MongoDB. Here is a complete example from the creators of Phactory in their guide explaining how to mock MongoDB in PHP.
Edit: The above links are now dead, and Phactory appears to be no-longer maintained. There's a new project called `php-mongomock' that aims to solve this problem:
use Helmich\MongoMock\MockCollection;
$collection = new MockCollection();
$collection->createIndex(['foo' => 1]);
$documentId = $collection->insertOne(['foo' => 'bar'])->insertedId();
$collection->updateOne(['_id' => $documentId], ['$set' => ['foo' => 'baz']]);
For those who might stumble across this later, I found a couple of solutions. In the case referenced above I realized that it is more appropriate to pass MongoCollection's in as dependancies, and those are very easy to mock.
However, as I am using Mockery for my mocking library, there are some options for handling "Demeter chains", like the one I asked specifically about:
$collection = $this->_mongo_client->vertical->primaryMember;
Here is a link to the docs:
http://docs.mockery.io/en/latest/reference/demeter_chains.html
It basically states that you can theoretically mock the chain like this:
$mock = \Mockery::mock('\MongoClient');
$mock->shouldReceive('vertical->primaryMember')->andReturn($mockMongoCollection);
You can return anything that you want, and seems like a pretty workable solution.

Less PHP is broken?

So I am using Less PHP to compile .less files down to .css and I decided to try out a small class I wrote where I take all the .less files in a directory and compile them to .css and move them to their own css directory.
So to test this I took all of twitter bootstrap less and placed it into a directory and then ran my class I wrote, see below, and found less php to be exploding: the current error is:
Error occurred:exception 'Exception' with message 'variable #alert-padding is undefined: failed at 'padding: #alert-padding;'
The class I wrote is just a simple wrapper:
class AisisLess_Less_Less{
protected $_less = null;
protected $_file = null;
public function __construct(){
$this->_file = new AisisCore_FileHandling_File();
$this->_less = new lessc();
$this->init();
}
public function init(){}
public function compile_directory($pattern, $compile_to_dir){
$less_files = $this->_file->get_directory_of_files($pattern);
$this->_compile($less_files, $compile_to_dir);
}
protected function _compile($array_of_files, $compile_directory){
foreach($array_of_files as $file){
$file_without_path = substr( $file, strrpos( $file, '/' )+1 );
try{
$css_file = preg_replace('"\.less"', '.css', $file_without_path);
$this->_less->checkedCompile($file, $compile_directory . $css_file);
}catch(exception $e){
throw new AisisLess_Exceptions_LessException('Error occurred:' . $e);
}
}
}
}
The concept is you use as such:
$less = new AisisLess_Less_Less();
$less->compile_directory(path/to/less/*.less, path/to/compiled/out/put);
My code is just a simple wrapper. The actual error is being thrown by the less library on some variable thing which is apart of the bootstrap less files.
So is the library acting up or is bootstrap failing at coding or have I screwed something up?
And if so how do I fix this?
What I suspect (no proof). If I understand you correctly, you just have the directory of bootstrap files and your code runs through them sequentially compiling them to css. That means the first file it is trying to compile is alerts.less which, low and behold, the very first variable reference in that file is (as of this writing) on line 10:
padding: #alert-padding;
This matches your error.
The issue is most likely that the bootstrap files are not designed to just be blindly compiled, because in most cases each is its own little piece, but requires other key pieces to compile (like variables.less and mixins.less). This is why, generally speaking, you only compile the bootstrap.less file which then will #import all the necessary files.
Basically, if I understand correctly what you have designed and how it works, you picked a bad set of files to run tests on because they are not designed to work that way. Better to just create a few small less files that are independent of each other for testing. However, what this has revealed is that your plan has a potential flaw, in that it will only function on standalone files, so you cannot blindly run any .less file through it, or you will get such compile errors because of broken dependencies.

Load PHP view file from controller with given parameters

I have a simple controller file StudentController.php
<?php
$data = array();
$data["firstName"] = $_GET["firstName"];
loadView("StudentView.php", $data);
?>
I have a even simpler view file called StudentView.php
<?php
echo $firstName;
?>
I have absolutely no idea how to implement loadView($view, $data) function. I want variables from $data in controller became available in view ($data["foo"] from controller became $foo in view)
I want achieve what is very easy to do in CodeIgniter but I have no idea how it is implemented. I tried to look into Controller.php and Loader.php in source files, but it was too messy for me to understand.
I don't want to use CodeIgniter or any other framework, I want to natively do in PHP.
If you're going to be building a large website to be used by the general public, a framework is generally a good idea for multiple reasons:
Properly unit tested - generally speaking, you can be confident that the core of your website is functioning as it's intended to
Community support - have questions and you can easily get answers; generally these frameworks are open sourced and usually actively developed by the PHP community
Secure - Framework developers are, well, framework developers; and they have been trained to write code with security as a top priority
However, this question has nothing to do with frameworks vs not, so I'll answer the question you asked with a very simple function:
function loadView($view, $data) {
extract($data);
ob_start();
require_once $view;
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}
You can chose to return the contents or print them directly, but that function should do what you need. I'm making no guarantees about the security of this code, and I have obviously done no error checking. But it should serve as a great foundation to get you started.

Zend Framework Class Displaying Code

I have a Zend Framework application running on a local web server. I've run into an issue where it displays the code for certain classes. It looks like the autoloader isn't working. Whenever it tries to use a class that should have been autoloaded, it crashes saying it can't find the class, and prints the contents of the php file containing the class it was looking for.
Here's my autoloader
protected function _initAutoload()
{
echo "in autoload";
// Set up autoload.
$obj_loader = Zend_Loader_Autoloader::getInstance();
$obj_loader->setFallbackAutoloader(true);
$obj_loader->registerNamespace('Gutterbling_');
return $obj_loader;
}
The class that can't be found is Gutterbling_Acl. It doesn't say the file can't be found, just the class.
Warning : dirty quick answer.
A look in one of my Zend app and I've seen this line just before the return statement (and I don't have the call to setFallbackAutoloader) :
$obj_loader = new Zend_Application_Module_Autoloader(array(
'namespace' => '',
'basePath' => APPLICATION_PATH));
Add it and test.
Ok... Sorry to waste everyone's time. The problem was, the remote server has php short tags enabled. The local server does not. The files that aren't working start with
Again, sorry about that. Hopefully this helps someone with the same problem.

Translating paths in PHP from (*nix) server to (winxp) dev machine

I'm working on a PHP project that has a lot of hard coded paths in it. I'm not the main developer, just working on a small part of the project.
I'd like to be able to test my changes locally before committing them, but my directory structure is completely different. For example, there's a lot of this in the code:
require_once("/home/clientx/htdocs/include.php")
Which doesn't work on my local WAMP server because the path is different. Is there a way to tell either WAMP or XP that "/home/clientx/htdocs/" really means "c:/shared/clients/clientx"?
If its a local copy, do a search and replace on the whole directory , Please don't forget trailing slash. And when you commit the code, do reverse.
This is the solution, if you don't want to add extra variables and stuff (because that would change other developers' code/work/dependencies (if any)
search "/home/clientx/htdocs/" and replace to this: "c:/shared/clients/clientx/"
Always use $_SERVER['DOCUMENT_ROOT'] instead of hardcoded path.
require_once($_SERVER['DOCUMENT_ROOT']."/include.php")
as for your wamb environment, you will need a dedicated drive to simulate file structure. You can use NTFS tools or simple subst command to map some directory to a drive.
Create /home/clientx/htdocs/ folder on this drive and change your httpd.conf to reflect it.
But again, you will do yourself a huge favor by convincing your coworkers to stop using hardcoded paths
WARNING: ONLY USE THIS SOLUTION FOR EMERGENCY REPAIRS, NEVER FOR LONGER PRODUCTION CODE
Define a class with rewriting methods, see http://php.net/manual/en/class.streamwrapper.php
<?php
class YourEmergencyWrapper {
static $from = '/home/clientx/htdocs/';
static $to = 'c:/shared/clients/client';
private $resource = null;
//...some example stream_* functions, be sure to implement them all
function stream_open($path,$mode,$options=null,&$opened_path){
$path = self::rewrite($path);
self::restore();
$this->resource = fopen($path,$mode,$options);
self::reenable();
$opened_path = $path;
return is_resource($this->resource);
}
function stream_read($count){
self::restore();
$ret = fread($this->resource,$count);
self::reenable();
return $ret;
}
function stream_eof(){
self::restore();
$ret = feof($this->resource);
self::reenable();
return $ret;
}
function stream_stat(){
self::restore();
$ret = fstat($this->resource);
self::reenable();
return $ret;
}
static function rewrite($path){
if(strpos($path,self::$from)===0) $path = self::$to.substr($path,strlen(self::$from));
return $path;
}
//... other functions
private static function restore(){
stream_wrapper_restore('file');
}
private static function reenable(){
stream_wrapper_unregister('file');
stream_wrapper_register('file',__CLASS__);
}
}
stream_wrapper_unregister('file');
stream_wrapper_register('file','YourEmergencyWrapper');
Seriously, only some local debugging on your own dev-server. You can force it as an auto_prepend on almost any code. Left some function yet be implemented ;P

Categories