Ryan Williams
Ryan Williams

Reputation: 967

Unit testing a PHP Framework

I have a custom Lightweight PHP framework that our company uses. To start, I don't want the suggestion to use a third-party framework. I am trying to write unit tests for it but I have stumbled upon a problem. I am relatively new to unit testing, but most of the code is already covered.

I have a loader class that will require files that can't be loaded with a class auto loader (e.g. Configuration files). It has a kind of fallback system where if you don't specify a module to load a particular resource from, it will try to guess based on the current module, then the default module. This has required me (so far as I can see) to check for the existence of a particular file before falling back to the default.

The problem I'm running into is writing a test for the class that doesn't actually depend on any files existing (which is obviously desirable). I've read about virtual file systems and testing, but I'm having trouble seeing how I can utilize it to solve my problem.

I can write an abstraction of the filesystem that I inject into this class, but then that class will have a similar problem.

In pseudo code: if file exists in this module, load it; if not, load the default.

Any suggestions are greatly appreciated.

EDIT:

Here's an excerpt of the code:

class Base implements Loader {

...

protected function _include( $type, $name ){
    $identifier = $this->parseName($name);

    $path = '/'.$this->resource_types[$type]['directory'].'/'.$this->resource_types[$type]['file_prefix'].$identifier['name'].'.php';

    if( $identifier['module'] && is_file(Core::basePath().'/'.Constant::get('MODULE_PATH').'/'.$identifier['module'].$path) ){
        Core::requireRoot('/'.Constant::get('MODULE_PATH').'/'.$identifier['module'].$path);

    } else if( is_file(Core::basePath().'/'.Constant::get('CORE_PATH').$path) ){
        Core::requireRoot('/'.Constant::get('CORE_PATH').$path);

    } else {
        throw new Exceptions\Load(ucwords($type).' '.$name.' not found');
    }
}

....

}

This is a very low-level class, so it has a few static calls that are basically substitutes for or wrappers around native PHP functions. Core::requireRoot is just a wrapper around the native require function. Constant::get is a custom implementation of constants (an alternative to define, then using the constant directly). Keep in mind I'm not actually testing this method directly. I have public methods such as loadHooks that I am actually testing that call this method behind the scenes.

Upvotes: 1

Views: 310

Answers (1)

Ryan Williams
Ryan Williams

Reputation: 967

Thanks both to @hek2mgl and (to a lesser extent) @deceze (who was correct about my static calls), I have a solution to this problem. It is not the kind of solution I originally hoped for, but I think it's the best that can be done. Here goes.

I am going to use temporary files, as per @hek2mgl's suggestion here (github.com/metashock/Jm_Autoloader). I don't think there is any way around this, even with a filesystem abstraction object. That said, I am going to use a filesystem abstraction object which I'll inject into this class, because it will make it easier to re-use the temporary file substitution in other contexts.

I am going to remove the static calls in that function (which I knew better than to do, but it can be easy to convince yourself that "this is such a low-level class it actually doesn't matter"). Instead I will create getter and setter methods for the CORE_PATH and MODULES_PATH, and revert to native call to require. This does not need to be done with a new filesystem object, but I think it is a lot cleaner and more flexible, and more loosely coupled.

Upvotes: 1

Related Questions