Action-Domain-Responder in WordPress – 02

Adding some lower level tests.

Previously

In my previous post I’ve introduced my attempt to implement the Action-Domain-Responder pattern in a WordPress context using a WordPress plugin.
I’m building, in this post, on the code and considerations of that previous one and I suggest it as an introductory reading.
I’ve introduced, in that, the use of the testing tools: Codeception and wp-browser, and that of the production tools: DI52 for dependency injection management, klein52 for the routing.
I concluded the post passing a first acceptance test to make sure I could, with no user-land interaction involving the creation or manipulation of pages, see the list of the last published posts at the aptly named /posts page.
The code of that first iteration can be seen on the repository, tagged dev-1.

Adding some lower level testing

I’ve written a first acceptance test and put together the minimum code needed to pass that trying to stick with the ADR pattern; it’s now time to add some lower level tests: some unit level ones, ideally.
Looking at the current non-boilerplate code I want to test three classes:

  • adr_Actions_ViewPosts – the first action implementation; that depends upon…
  • adr_Responders_Responder – a class depending on the Twig library and…
  • adr_Domains_Domain – a wrapping around the WP_Query class

I will start from the last one as it makes for a more interesting case.
The current implementation of the class is this:

// file src/adr/Domains/Domain.php

class adr_Domains_Domain {

    public function get( array $args ) {
        $query = new WP_Query( $args );

        return $query->have_posts() ? $query->posts : array();
    }
}

It gets the job done but presents a fundamental flaw from a test-driven development perspective: the WP_Query class is an hard-coded dependency that is built in the context of the get method itself; as such there is no way for me to mock that $query dependency in a clean way.
The first modification I do is, then, to add a constructor method explicitly declaring an instance of the WP_Query class as a dependency:

// file src/adr/Domains/Domain.php

class adr_Domains_Domain {

    /**
     * @var \WP_Query
     */
    protected $query;

    public function __construct( WP_Query $query ) {
        $this->query = $query;
    }

    public function get( array $args ) {
        $this->query->parse_query( $args );
        $posts = $this->query->get_posts();

        return $this->query->have_posts() ? $posts : array();
    }
}

Running the only available acceptance test I confirm this modification is not “breaking” the code:

Once this dependency is injected it can be controlled and testing the class becomes easier; here is the final code for the class unit test case:

namespace adr\Domains;

use adr_Domains_Domain as Domain;

// dynamically load WordPress root folder from Codeception configuration file, see codeception.yml
include_once \Codeception\Configuration::config()['wpRootFolder'] . '/wp-includes/class-wp-query.php';

class DomainTest extends \Codeception\Test\Unit {

    /**
     * @var \UnitTester
     */
    protected $tester;

    /**
     * @var \WP_Query
     */
    protected $query;

    protected function _before() {
        $this->query = $this->prophesize( \WP_Query::class );
    }

    /**
     * @return Domain
     */
    private function make_instance() {
        return new Domain( $this->query->reveal() );
    }

    /**
     * @test
     * it should be instantiatable
     */
    public function it_should_be_instantiatable() {
        $sut = $this->make_instance();

        $this->assertInstanceOf( Domain::class, $sut );
    }

    /**
     * It should call a WP_Query with the provided arguments
     *
     * @test
     */
    public function should_call_a_wp_query_with_the_provided_arguments() {
        $args = [ 'foo' => 'bar' ];
        $this->query->parse_query( $args )->shouldBeCalled();
        $this->query->get_posts()->shouldBeCalled();
        $this->query->have_posts()->shouldBeCalled();

        $sut = $this->make_instance();

        $sut->get( $args );
    }

    /**
     * It should return the query result if posts are found matching the query
     *
     * @test
     */
    public function should_return_the_query_result_if_posts_are_found_matching_the_query() {
        $args  = [ 'foo' => 'bar' ];
        $posts = [ 'some' => 'posts' ];
        $this->query->parse_query( $args )->shouldBeCalled();
        $this->query->get_posts()->willReturn( $posts );
        $this->query->have_posts()->willReturn( true );

        $sut = $this->make_instance();

        $this->assertEquals( $posts, $sut->get( $args ) );
    }

    /**
     * It should return an empty array if no posts were found matching the query
     *
     * @test
     */
    public function should_return_an_empty_array_if_no_posts_were_found_matching_the_query() {
        $args = [ 'foo' => 'bar' ];
        $this->query->parse_query( $args )->shouldBeCalled();
        $this->query->get_posts()->willReturn( 'foo' );
        $this->query->have_posts()->willReturn( false );

        $sut = $this->make_instance();

        $this->assertEquals( [], $sut->get( $args ) );
    }
}

Worth noting here is I’ve defined a wpRootFolder parameter at the root of Codeception configuration file, it is filled at runtime using Codeception dynamic parameter configuration:

paths:
    tests: tests
    output: tests/_output
    data: tests/_data
    support: tests/_support
    envs: tests/_envs
actor_suffix: Tester
extensions:
    enabled:
        - Codeception\Extension\RunFailed
    commands:
        - Codeception\Command\GenerateWPUnit
        - Codeception\Command\GenerateWPRestApi
        - Codeception\Command\GenerateWPRestController
        - Codeception\Command\GenerateWPRestPostTypeController
        - Codeception\Command\GenerateWPAjax
        - Codeception\Command\GenerateWPCanonical
        - Codeception\Command\GenerateWPXMLRPC
        - Codeception\Command\DbSnapshot
        - tad\Codeception\Command\SearchReplace
params:
  - .env
wpRootFolder: "%WP_ROOT_FOLDER%"

This allows me, in the test file, to include the /wp-includes/class-wp-query.php file no matter how the testing environment is configured. Hard-coding file paths in relation to a known file structure is not a crime in tests but getting into the habit of thinking about CI environments and different setups is usually a good idea.
I’m also using the make_instance pattern I’ve grown fond of to mock the “subject under test” ( hence the “sut” variable name) dependencies in the _before, or setUp, method of the test case, setting them up in the test method and then building the object knowing my ad-hoc and set up dependencies will be injected; this comes especially handy when the dependency injection is done via the object constructor method and not through factory or setter methods.

The test code for the adr_Responders_Responder and adr_Actions_Action classes is pretty much on the same line and not really revolutionary; the code shown here is on GitHub tagged dev-2.
Running the whole acceptance and unit test suites I verify everything clicks in place:

Note that I’m not running the suites all at the same time using codecept run, but one after the other using:

codecept run acceptance && codecept run unit

While this is not strictly necessary at the moment once I add tests depending on WordPress code it will become fundamental to make sure test will not interfere one with the other due to WordPress fondness for global variables and constants.

Next

I will work next to make sure the plugin more deeply integrates in tried and trusted WordPress mechanics putting in place the first integration tests.

I appreciate your input