Dressing a magic adapter class for tests

While making the development of OO plugins way easier my developer-oriented Adapter Classes for WordPress lost its testing potential taking the path of dynamic method and property definition via some magic methods but, sadly, lost its ability to be used in tests.

I need a plan to fake properly

As detailed in the above linked posts now the adapter classes all present no defined methods, some utility ones really, to the PHPUnit mocking engine and the produced mocks will not be usable in tests because I will not be able to set expectations and behaviours of methods not existing in the original class.
I need to fill the class with methods before mocking it to actually make the mocks work, to do this I need a list of the functions the adclasses_Functions class will wrap at runtime during normal WordPress runs.

Get the defined functions and dump them

A patchy solution, the one I’ve been using now, is to save a list of defined functions at run time using PHP get_defined_functions function to have an array of them and then print them on screen somewhere during a normal (local really) WordPress session and save those functions to a file manually

class adclasses_Ajax_Controller(){

    //here be functions that hook into WordPress ajax to call the function below at the press of a button

    function get_defined_functions(){
        $functions = get_defined_functions();
        $contents = implode(',', $functions);

        return $contents;
    }

}

Pretty easy really but very unsafe and inefficient but will do for the moment and the version I’m testing is not distributed to clients.

I got plans and the will to build a façade

At test-time, when WordPress is not running, I will fill my adclasses_Functions class with fake methods using runkit via an utility method defined in the adclasses_Mockable_Adapter interface

class adclasses_Functions implements adclasses_Mockable_Adapter {

    ...


    /**
     * Fills the class methods and properties with empty and returning null façades to be able to mock them.
     * @return bool True if the façade building was successful, false otherwise.
     */
        public static function build_facade()
    {
        $path = dirname(__FILE__) . '/../assets/functions.txt';
        $contents = file_get_contents($path);
        $functions_to_facade = explode(',', $contents);

        foreach ($functions_to_facade as $function_name) {
            $args = array(
                'adclasses_Functions',
                $function_name,
                '',
                'return null;'
            );
            runkit_method_add($args);
        }
    }
}

the call sequence, in the Factory class, is simply to build the façade of each class and then mock (which means instance) it

...

foreach ($adadpter_to_load as $adapter_class_slug) {
        $class_name = 'adclasses_' . ucfirst($adapter_class_slug);
        // init the class façade to mock creating class methods
        // and properties to actually have something to mock
        // current class stubbing or mocking will only go on if the façade building
        // was successful
        if (call_user_func(array(
            $class_name,
            'build_facade' // build a method façade before trying to mock (hence instance) the class
        ))) {
            // get an instance of the adapter with stubbed methods
            if (!$return_mocks) {
                $adapters[$adapter_class_slug] = $this->getMock($class_name);
            }
            // get an instance of the adapter with unchanged methods but null_method
            // null_method is a method defined in each class implementing the adclasses_Mockable_Adapter interface
            else {
                $adapters[$adapter_class_slug] = $this->getMock($class_name, array(
                    'null_method'
                ));
            }
        }
    }

...

No good-doers returning nothing but I like them

Any mock or stub of the adclasses_Functions class will now be filled with methods that do nothing and return null but they all are there. In my test code I can now define their expectations and behaviours without PHPUnit silently dying

...

// get an array of properly mocked adapters
$mocks = $this->get_mock_adapters(); 
// this would not work if an 'add_action' method did not exist in the class before getting a mock instance of it!
$mocks['functions']->expects($this->once())->method('add_action')->will($this->returnValue(true));

...

Next steps

Copy and pasting is not the way to go, keeping the list of functions up to date manually is not an option I want to explore and having the list dumped automatically during normal plugin work is not the most efficient use of a server capacity and resources.
What I’m implementing now is an AJAX action to write the list to file on command using the Filesystem API to do so. Updates will be uploaded in the GitHub repository.

2 thoughts on “Dressing a magic adapter class for tests

I appreciate your input