1

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.

24
  • Testing very much depends on the ability to mock dependencies. If a piece of code has such hardcoded dependencies that they can't be mocked, the code is simply hard/impossible to test. In that case some actual code sample would help to see if there's indeed nothing to hook into. Commented Aug 24, 2013 at 8:04
  • I think you are right , though the dependencies you are talking about are native php functions. I can write an abstraction of the file system and inject that. But I still have to figure out how to determine which file was required. Commented Aug 24, 2013 at 8:09
  • Trying to emulate a file system is going way too low for a unit test. Your code should use an abstraction to get the config files. Something like a Config class which you inject into the code, and the code uses $config->get('foo.bar') to load those files. That's easily mockable. A hardcoded fopen('config/foo/bar') is virtually impossible to mock. Commented Aug 24, 2013 at 8:13
  • Maybe kunststube.net/static can lend some more perspective here. Commented Aug 24, 2013 at 8:18
  • I will think about that but it doesn't seem to get around the issue I've presented (then again maybe I am going about it the wrong way and have everything contained in a class). But to be clear, when I say configuration files I'm talking about more than your example. I'm talking About files that define and attach hooks to the application, etc. Commented Aug 24, 2013 at 8:22

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.