QA Thing – test-driven developing it 05

Getting to unit tests!

Part of a series

This post is the fifth one in a series of posts I’m publishing to chronicle my personal test-driven development flow to develop a WordPress plugin.
The first post in the series could be better starting points than this post.

##The starting code and a the tools I’m pushing the code to GitHub as I work tagging it when a relevant change happens.
The code I’m starting from for this post is tagged post-04 on GitHub.
As tools of the trade I’m relying on wp-browser and Codeception for the testing, DI52 as dependency injection container and xrstf PHP 5.2 compatible autoloader for Composer to autoload the classes.

Of adapters and men

Setting my mind on the task of writing some lower level tests I will start with the qa_Configurations_Scanner class; as the name suggests the class will “scan” the plugins installed on the local installation looking for “QA Thing” configuration files.
The class current code is the one below:

class qa_Configurations_Scanner implements qa_Configurations_ScannerI {
    /**
     * Returns an array of the available configurations.
     *
     * @return qa_Configurations_ConfigurationI[]
     */
    public function configurations() {
        $configurationProviders = $this->scanPlugins();

        if ( empty( $configurationProviders ) ) {
            return array();
        }

        return call_user_func_array( 'array_merge', array_map( array( $this, 'buildConfigurations' ), $configurationProviders ) );
    }

    protected function scanPlugins() {
        if ( ! function_exists( 'get_plugins' ) ) {
            include_once ABSPATH . '/' . WPINC . '/plugin.php';
        }

        $plugins = get_plugins();

        if ( empty( $plugins ) ) {
            return array();
        }

        array_walk( $plugins, array( $this, 'addPaths' ) );

        $plugins = array_filter( $plugins, array( $this, 'hasConfig' ) );

        return $plugins;
    }

    protected function addPaths( array &$data, $path ) {
        $data['path'] = WP_PLUGIN_DIR . '/' . $path;
        $data['root'] = dirname( WP_PLUGIN_DIR . '/' . $path );
    }

    protected function hasConfig( array $data ) {
        return is_readable( $data['root'] . '/qa/qa-config.json' );
    }

    protected function buildConfigurations( array $data ) {
        $info = file_get_contents( $data['root'] . '/qa/qa-config.json' );
        $info = (array) json_decode( $info );
        $configurations = array();
        foreach ( $info['configurations'] as $id => $configuration ) {
            $configurations[] = new qa_Configurations_Configuration( $id, (array) $configuration, $data );
        }

        return $configurations;
    }
}

And while not strange in WordPress terms it presents some typical “testing traps” of WordPress applications: its use of constants (like ABSPATH or WPINC) and functions like get_plugins() makes it nigh impossible to test on a unit level.
The class does depend on globally defined functions and constants and hence those are dependencies; the problem lies in the “unmockability” of those dependencies.
At runtime there is no way to re-define a constant and there is no way to re-define a function implementation.
For the latter problem solutions like runkit or function-mocker exist but those all present the problem of implying knowledge of the mocked object without controlling its code.
I can use function-mocker to mock the get_plugins() function like this:

namespace qa\Configurations;

use qa_Configurations_Scanner as Scanner;

class ScannerTest extends \Codeception\Test\Unit
{
    /**
     * @var \UnitTester
     */
    protected $tester;

    /**
     * @test
     * it should return no configurations if there are no plugins installed
     */
    public function it_should_return_no_configurations_if_there_are_no_plugins_installed() {
        $scanner = new Scanner();

        FunctionMocker::replace('get_plugins', function(){
            return [];
        });

        $this->assertEmpty($scanner->configurations());
    }

    /**
     * @test
     * it should return no configurations if there is one plugin installed but has no configurations
     */
    public function it_should_return_no_configurations_if_there_is_one_plugin_installed_but_has_no_configurations() {
        $scanner = new Scanner();

        FunctionMocker::replace('get_plugins', function () {
            return [
                'foo/foo.php' => [
                    'Name' => 'Foo Plugin',
                    'PluginURI' => 'http://foo.dev',
                    'Version' => '0.1.0',
                    'Description' => 'Foo Plugin',
                    'Author' => 'Foo Bar',
                    'AuthorURI' => 'http://foo.dev',
                    'TextDomain' => 'foo',
                    'DomainPath' => '/languages',
                    'Network' => false,
                    'Title' => 'foo Plugin',
                    'AuthorName' => 'Foo Bar III'
                ]
            ];
        });

        $this->assertEmpty($scanner->configurations());
    }
}

I am, but, using the knowledge of the get_plugins() code and return value to mock it in quantity and quality.
And while I’m testing code I own (the Scanner class) I’m mocking test I do not own and its implementation and format could change beyond my control.
Constants are, simply put, un-mockable and as such code relying on them is either not testable or scarcely so.
To avoid my code from depending on un-mockable dependencies making it, subsequently, nigh untestable, I will apply the adapter pattern.

A WordPress adapter

The purpose of an adapter is to create a stupid middleman between code I own and want to test and code that I do not own; the adapter is, by definition, untestable on a unit level.
Calling in the “pattern” word makes it sound more complicated than it is, the code below presents a simple and tailored adapter:

<?php

class qa_Adapters_WordPress implements qa_Adapters_WordPressI {
    /**
     * Proxy for the `get_plugins` function
     *
     * @see get_plugins()
     *
     * @return array
     */
    public function get_plugins($plugin_folder = '') {
        if (!function_exists('get_plugins')) {
            include_once ABSPATH . '/' . WPINC . '/plugin.php';
        }

        return get_plugins($plugin_folder);
    }

    /**
     * Gets the WP_PLUGIN_DIR constant.
     *
     * @param null|string $path An optional path to append
     *
     * @return string
     */
    public function plugin_dir($path = null) {
        if (null !== $path) {
            return trailingslashit(WP_PLUGIN_DIR) . ltrim($path, DIRECTORY_SEPARATOR);
        }
        return trailingslashit(WP_PLUGIN_DIR);
    }
}

And the qa_Configurations_Scanner class changes to use the new dependency:

<?php

class qa_Configurations_Scanner implements qa_Configurations_ScannerI {
    /**
     * @var qa_Adapters_WordPressI
     */
    protected $wp;

    public function __construct(qa_Adapters_WordPressI $wordPress) {
        $this->wp = $wordPress;
    }

    /**
     * Returns an array of the available configurations.
     *
     * @return qa_Configurations_ConfigurationI[]
     */
    public function configurations() {
        // as before
    }

    protected function scanPlugins() {
        $plugins = $this->wp->get_plugins();

        if ( empty( $plugins ) ) {
            return array();
        }

        array_walk( $plugins, array( $this, 'addPaths' ) );

        $plugins = array_filter( $plugins, array( $this, 'hasConfig' ) );

        return $plugins;
    }

    protected function addPaths( array &$data, $path ) {
        $data['path'] = $this->wp->plugin_dir($path);
        $data['root'] = dirname($this->wp->plugin_dir($path));
    }

    protected function hasConfig( array $data ) {
        return is_readable( $data['root'] . '/qa/qa-config.json' );
    }

    protected function buildConfigurations( array $data ) {
        // as before
    }
}

The code did not become less readable due to the adapter and I was able to remove the manual file inclusion.
Adapters offer another advantage as well: as natural “chokepoints” in the code if anything of the underlying code changes then I will have to update one method to deal with the new code in place of any class method using that code.
After adaptation and evolution is time to test.

The code after the tests

The code can be seen on the plugin repository tagged post-05.
I’m not pasting the updated classes and relative tests for sake of brevity but suffice to say the code passes the tests:

[caption id=“attachment_3286” align=“aligncenter” width=“1153”]Passing tests Passing tests[/caption]

Anyone curious can nose around on the repository.

Getting the code by the Handlebars

The only note I’d make is about the code I’m using to test the options page class (qa_Options_Page): it’s just one method.

namespace qa\Options;


use Prophecy\Argument;

class PageTest extends \Codeception\Test\Unit {
    protected $backupGlobals = false;
    /**
     * @var \UnitTester
     */
    protected $tester;
    /**
     * @var \qa_RenderEngines_HandlebarsI
     */
    protected $renderEngine;

    /**
     * @var \qa_Configurations_ScannerI
     */
    protected $scanner;

    protected function _before() {
        $this->renderEngine = $this->prophesize(\qa_RenderEngines_HandlebarsI::class);
        $this->scanner = $this->prophesize(\qa_Configurations_ScannerI::class);
    }

    /**
     * @test
     * it should delegate render to Handlebars
     */
    public function it_should_delegate_render_to_handlebars() {
        $this->scanner->configurations()->willReturn(['foo' => 'bar']);
        $this->renderEngine->render('options-page', Argument::type('array'))->shouldBeCalled();

        $page = new \qa_Options_Page('foo', $this->scanner->reveal(), $this->renderEngine->reveal());

        $page->render();
    }
}

While I’ve moved some logic from it to the qa_Configurations_Scanner class I’ve also tapped into Handlebars in its PHP 5.2 compatible incarnation to avoid testing the render method.
I’ve said in my last post that I will test any code that “forks” or “scales” and the render method contains both logics:

if there are configurations render an option for each configuration else render a “nothing found” message.

In place of re-inventing the wheel, and with it having to write new tests, I’ve used Handlebars to deal with the scaling and forking part:

{{#if configurations}}
    <select name="qa-configuration" id="qa-configuration">
        {{#each configurations}}
            <option value="{{this.id}}" class="qa-configuration-option">{{this.name}}</option>
        {{/each}}
    </select>
{{else}}
    <h2>{{nothing-found}}</h2>
{{/if}}

I will always go for the approach that allows me to write as little code as possible and third-party libraries are always a good starting point.

Next

I’ve done some acceptance testing, some functional testing and some unit testing: it’s time to write some integration testing to see how it fits in the picture and what role it has in a WordPress and TDD workflow.