A WordPress functional testing module 01

First alpha version of a functional WordPress module.

What’s WordPress functional testing about?

In as short a sentence as possible it means that, given Codeception and wp-browser:

  1. WordPress code base is loaded in the same variable scope as the tests
  2. WordPress will be freshly installed to start from database scratch on each test
  3. the test will interact with the WordPress code base in two ways:
    3a. sending GET, POST, PUT and DELETE HTTP requests directly to the index file (no web server here)
    3b. using WordPress defined functions and methods to set up the fixture
  4. each test will have WordPress printing/returning HTML, Json, XML et cetera as if answering an HTTP request
  5. assertions can be made using WPBrowser-like assertions as seeElement

This is something that’s currently not possible using wp-browser modules. See my previous post for details about why this is not currently supported and why anything that can be done now is not functional testing.

What obstacles does this pose?

In PHP terms a “long running process” is process that handles more than one request per life cycle.
Typical WordPress flow is:

  1. the web server receives an HTTP request
  2. the request is sent to WordPress index.php file
  3. the index.php loads WordPress
  4. WordPress parses the HTTP request contents
  5. WordPress decides what the response should be
  6. WordPress returns the response; this might be HTML for a browser, a Json string for a REST API request and so on
  7. WordPress finishes its execution

Functional testing would force a flow like this instead:

  1. WordPress is loaded before the test
  2. WordPress is installed before the test
  3. the request is sent to WordPress index.php file
  4. WordPress parses the HTTP request contents
  5. WordPress decides what the response should be
  6. WordPress returns the response; this might be HTML for a browser, a Json string for a REST API request and so on
  7. repeat point 1

WordPress is not meant to work this way but, to some extent and with some knowledge and attention, it can be done.

My first functional tests

To move my code in a direction I’ve set up the Codeception cest format test below:

<?php

/**
 * Class GeneralCest
 */
class GeneralCest
{
    /**
     * @test
     * it should be able to navigate to main page
     */
    public function it_should_be_able_to_navigate_to_main_page(WpmoduleTester $I)
    {
        $I->amOnPage('/');
    }

    /**
     * @test
     * it should be able to insert a post in the database
     */
    public function it_should_be_able_to_insert_a_post_in_the_database(WpmoduleTester $I)
    {
        $I->factory()->post->create(['post_title' => 'A post']);
        $I->amOnPage('/');
        $I->see('A post');
    }

    /**
     * @test
     * it should be able to navigate to a single post page
     */
    public function it_should_be_able_to_navigate_to_a_single_post_page(WpmoduleTester $I)
    {
        $id = $I->factory()->post->create(['post_title' => 'A post']);
        $I->amOnPage('/?p=' . $id);
        $I->see('A post');
        $I->seeElement('body.single');
    }

    /**
     * @test
     * it should not re-include header if already included in request
     */
    public function it_should_not_re_include_header_if_already_included_in_request(WpmoduleTester $I)
    {
        $id = $I->factory()->post->create(['post_title' => 'A post']);

        $I->amOnPage('/?p=' . $id);
        $I->seeElement('body.single');

        // header.php already included, not re-included, no body class
        $I->amOnPage('/?p=' . $id);
        $I->dontSeeElement('body.single');
    }

    /**
     * @test
     * it should allow re-setting template inclusion control in tests
     */
    public function it_should_allow_re_setting_template_inclusion_control_in_tests(WpmoduleTester $I)
    {
        $id = $I->factory()->post->create(['post_title' => 'A post']);

        $I->amOnPage('/?p=' . $id);
        $I->seeElement('body.single');

        $I->resetTemplateInclusions();

        $I->amOnPage('/?p=' . $id);
        $I->seeElement('body.single');
    }

    /**
     * @test
     * it should allow setting permalinks structure in tests
     */
    public function it_should_allow_setting_permalink_structure_in_tests(WpmoduleTester $I)
    {
        $I->factory()->post->create(['post_title' => 'A post', 'post_name' => 'a-post']);

        $I->setPermalinkStructure('/%postname%/');

        $I->amOnPage('/a-post');
        $I->seeElement('body.single');
    }
}

and worked my way (painfully) to a first working version of a functional tests WordPress module.

Challenges of WordPress as a long running process

I’ve directed my effort to allow functional testing of a WordPress-managed front-end for the time being. This means themes will be in the game and will have to be loaded and used on each request handling; WordPress will use the require_once directive to load the theme header, footer and sidebar using the well-known get_header, get_footer and get_sidebar template tags.

/**
 * @test
 * it should allow setting permalinks structure in tests
 */
public function it_should_allow_setting_permalink_structure_in_tests(WpmoduleTester $I)
{
    $I->factory()->post->create(['post_title' => 'A post', 'post_name' => 'a-post']);

    $I->setPermalinkStructure('/%postname%/');

    $I->amOnPage('/a-post');
    $I->seeElement('body.single');
}

The last test from the code example above would fail if one did let WordPress handle it so I had to do some filtering to make it work.
The test would fail as WordPress has already loaded the header.php file (during the it_should_be_able_to_navigate_to_main_page test), that would not be included again and WordPress would not print the single class on the body.
Another common practice is to use die to return responses (especially when serving AJAX requests); on that front nothing can be done but if the code uses WordPress defined, and filterable, wp_die function the response can still be printed out without killing the PHP process.
That’s just a small part of the challenges faced and to come but being able to have the test above all green in hours, and not days, work time gives me hope.

Configuration and requirements

The module will require a working WordPress installation and SQL database connection to work.
Notice the lack of a web server among the requirements.
This is nothing new and is common to almost any framework module.
The module Codeception configuration file looks unsurprisingly similar to that of the WPLoader module:

class_name: WpmoduleTester
modules:
    enabled:
        - \Helper\Wpmodule
        - WordPress
    config:
        WordPress:
            wpRootFolder: "/Users/Luca/Sites/codeception-acceptance"
            dbName: "codeception-tests"
            dbHost: "127.0.0.1"
            dbUser: "root"
            dbPassword: "root"
            wpDebug: true
            tablePrefix: "wp_"
            domain: "codeception-tests.dev"
            plugins: []
            activatePlugins: []
            booststrapActions: []

I have yet to test plugin and theme activation and deactivation process but first things first.

On GitHub

The module lacks documentation and finesse but it’s on GitHub for anyone interested to see.

Next

I will work to have an organic access to the backend side of WordPress world (/wp-admin) in functional tests and be able to iterate on the module.