The power of “go_to” and WordPress (kinda) functional tests

Going places in WordPress “kinda-functional” tests.

Under the skin

I’ve written about the new possibilities offered by the latest version of wp-browser and its WPLoader module in previous posts (here and here) and limited my example to the context of functional tests.
Below functional tests are, usually, integration tests that would tap into the full power of the ‘WPLoader’ module in the context of a PhpUnit like test case extending the base WPUnitTestCase implemented by wp-browser.
Levels of testing are, but, gross simplifications that fit most of the time but could not fit every time.
Sometimes an additional level of testing between integration and functional is required.
This additional level might require:

  • exercising WordPress request handling system from its routing system down in a way similar to what functional testing would do…
  • …making, but, assertions about the state of WordPress itself in the context of the request handling.

This sounds confusing enough to deserve a code example.

Do parse request like I want

One of WordPress most powerful filters is the do_parse_request one.
It allows developers to “bail out” of the WordPress managed request handling to handle the request themselves.
The reasons for such a choice could be many but I’m concentrating on a very specific example: I want to implement a /posts route in my WordPress installation that will show a list of the latest posts.
The usual way to do it would be to either create page and assign a custom template to it or register a permalink structure that will rewrite to the /index.php/?post_type=post URL.
Filtering do_parse_request offers a third alternative.
This is the code of the plugin I’m using (I’m skipping the header to save lines):

add_filter('do_parse_request', function ($shouldHandle, WP $wp) {

    // parse the request 
    $components = parse_url($_SERVER['REQUEST_URI']);

    // if it's not bound for `/posts` let WP do its job
    if (empty($components['path']) || $components['path'] !== '/posts') {
        // let WordPress handle it
        return $shouldHandle;
    }

    global $wp_query;

    // set up the query
    $wp_query = new WP_Query(['post_type' => 'post', 'posts_per_page' => -1]);

    // set the 
    $wp->query_vars = [];

    // and the query vars on it
    $wp_query->is_archive = true;
    $wp_query->is_post_type_archive = 'post';

    // render the post archive page
    do_action('wp_head');

    if ($wp_query->have_posts()) {
        echo '<ul>';
        /** @var \WP_Post $post */
        foreach ($wp_query->posts as $post) {
            echo "<li>{$post->post_title}</li>";
        }
        '</ul>';
    } else {
        echo "No posts!";
    }

    do_action('wp_footer');


    // we handled it WP! You can let go.
    return false;
}, 1, 2);

Now, if I wanted to test that hitting /posts on the website would show me the latest post I could use an acceptance test; but what if I wanted, instead, to check that the query vars are properly set on the query?
This is relevant because plugins that print content-sensitive information in the header, like the SEO handling ones, need to know if a page is_archive(), is_homepage(), is_single(), and that information is fetched looking up properties on the global $wp_query object.
If I run a functional test, due to the isolation limitations imposed by WordPress “conquering” nature, I would not be able to know if, in the context of the request handling, the properties on the $wp_query object have been properly set.

Kinda integration, kinda functional

For such a case I can create a dedicated suite, queries for example:

codecept generate:suite queries

And configure it to use the WPLoader module in its default application; the plugin is called posts:

# Codeception Test Suite Configuration

# Suite for integration tests.
# Load WordPress and test classes that rely on its functions and classes.


class_name: QueriesTester
modules:
    enabled:
        - \Helper\Queries
        - WPLoader
    config:
        WPLoader:
            wpRootFolder: /home/luca/Sites/wp
            dbName: wpTests
            dbHost: localhost
            dbUser: root
            dbPassword: root
            tablePrefix: int_wp_
            domain: wp.localhost
            adminEmail: [email protected]
            title: WP Tests
            plugins: ['posts/posts.php']
            activatePlugins: ['posts/posts.php']

I add a test case to it:

wpcept generate:wpunit queries GoTo

The test I end up writing leverages the go_to() method provided by the WordPress Core testing suite base test case that works like WPBrowser::amOnPage().
The huge difference, the one I’m leveraging here, is that WordPress globals and constants are being set in the same scope as the test making it very powerful and very dangerous; the comment to the function reads:

note: the WP and WP_Query classes like to silently fetch parameters from all over the place (globals, GET, etc), which makes it tricky to run them more than once without very carefully clearing everything

Just so I know.
After some tweaking here is the test code:

<?php

class GoToTest extends \Codeception\TestCase\WPTestCase {

    /**
     * It should allow to "go to" the homepage
     *
     * @test
     */
    public function it_should_allow_to_go_to_the_homepage() {
        $this->go_to('/');

        $this->assertTrue(is_home());
        $this->assertFalse(is_singular());
        $this->assertFalse(is_admin());
    }

    /**
     * Test going in the admin area
     *
     * @test
     */
    public function test_going_in_the_admin_area() {
        $this->go_to('/wp-admin');

        $this->assertFalse(is_admin());
        $this->assertFalse(is_404());
    }

    /**
     * Test going on a post single page
     *
     * @test
     */
    public function test_going_on_a_post_single_page() {
        $id = $this->factory()->post->create();

        $this->go_to("/?p={$id}");

        $this->assertTrue(is_singular());
        $this->assertTrue(is_single());
        $this->assertFalse(is_home());
    }

    /**
     * Test going on a post single page using the permalink
     *
     * @test
     */
    public function test_going_on_a_post_single_page_using_the_permalink() {
        $this->factory()->post->create(['post_name' => 'foo']);

        $this->go_to('/foo');

        $this->assertFalse(is_404());
        $this->assertFalse(is_singular());

        $this->set_permalink_structure('%postname%');

        $this->go_to('/foo');

        $this->assertFalse(is_404());
        $this->assertTrue(is_singular());
        $this->assertTrue(is_single());
    }

    /**
     * Test my request parsing plugin
     *
     * @test
     */
    public function test_my_request_parsing_plugin() {
        $this->set_permalink_structure('%postname%');

        $this->factory()->post->create_many(10);

        ob_start();
        $this->go_to('/posts');
        ob_end_clean();

        // set wp_query back to the one set by the handling plugin

        $this->assertFalse(is_404());
        $this->asserttrue(is_archive());
        $this->asserttrue(is_post_type_archive('post'));
        $this->assertfalse(is_singular());
    }
}

And its passing colors:

Use with caution

This kind of assertion I could only make either with complicated and brittle acceptance tests verifying, as an example, that an header that should be printed by a certain SEO plugin on the head prints a certain meta; or I could print the query variables in a way in the head adding debug JSON objects and CSS classes to the markup.
I find this method cleaner and more maintainable and suiting my personal distaste for any output based test.
As the note says, though, WordPress expansionism will now be free to “invade” the tests variable scope with the potential of false positives and negatives due to correlation and lingering global state.

I appreciate your input