Using the WordPress functional test module 01

Setting up a plugin to run some functional testing.

Tongues

To test the new functional tests dedicated wp-browser module, WordPress, I will develop yet another multi-language plugin.
This is a toy project intended to experiment and I will probably drop it once all the tests I’m interested in will be in.

On GitHub

This code I present here is tagged 0.0.1 on GitHub.

Setting up Composer

I’ve created a basic Composer configuration file in the plugin root folder and required all the production and development dependencies I will be using:

{
  "name": "lucatume/tongues",
  "description": "Yet another WordPress language plugin",
  "minimum-stability": "stable",
  "license": "GPL-2.0",
  "authors": [
    {
      "name": "Luca Tumedei",
      "email": "luca@theaveragedev.com"
    }
  ],
  "require-dev": {
    "lucatume/wp-browser": "^1.15"
  },
  "require": {
    "php": ">=5.4",
    "lucatume/di52": "^1.3"
  },
  "autoload": {
    "psr-4": {
      "Tongues\\": "src/"
    }
  }
}

I will require PHP version 5.4, wp-browser and Codeception for the tests and the di52 dependency injection container to handle dependency injection and code organization.
I’ve set up the project initializing Composer and wp-browser from the plugin root folder:

composer update
./vendor/bin/wpcetp bootstrap

Functional suite configuration

The bootstrap command did create suites configuration files for me; the acceptance, integration and unit suites are the usual ones but the functional.suite.yml file is worth some additional consideration:

# Codeception Test Suite Configuration

# Suite for WordPress functional tests.
# Emulate web requests and make the WordPress application process them.


class_name: FunctionalTester
modules:
    enabled:
        - \Helper\Functional
        - Filesystem
        - WPDb:
            dsn: 'mysql:host=127.0.0.1;dbname=lang'
            user: root
            password: root
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: 'http://lang.dev'
            tablePrefix: wp_
        - WordPress:
            depends: WPDb
            wpRootFolder: /Users/Luca/Sites/lang
            adminUsername: admin
            adminPassword: admin

The functional suite depends on the WPDb module to work and, beside that, will have to be pointed to the WordPress installation root folder, the one containing the plugin I’m testing, and provided the admin user login information.

Boilerplate setup

After the suites configuration files are set up it’s time to provide the WPDb module the database dump that will constitute the database initial fixture for each acceptance and functional test.
I’ve set up the multisite network to contain 3 blogs:

  • the default one at http://lang.dev with a blog_id of 1
  • the Italian one at http://it.lang.dev with a blog_id of 2
  • the French one at http://fr.lang.dev with a blog_id of 3

and activated the currently empty “Tongues” plugin. This will be my functional and acceptance database testing fixture and I will use WP-CLI to dump the database in the Tongues plugin tests/_data folder:

wp db export ./Users/Luca/Sites/lang/wp-content/plugins/tongues/tests/_data/dump.sql

Before I write any code I will run Codeception to make sure all the modules are working as intended:

codecept run

Codeception empty run

The plugin initial code is the bare-bones code needed to make it register as a plugin in WordPress and be able to activate it:

<?php
/**
 * Plugin Name: Tongues
 * Plugin URI: http://theAverageDev.com
 * Description: Yet another WordPress language plugin
 * Version: 1.0
 * Author: theAverageDev
 * Author URI: http://theAverageDev.com
 * License: GPL 2.0
 */

include 'vendor/autoload.php';

The last line will take care to include the autoload file Composer generated for me.

Preliminary decisions

As the description states the plugin is another variation of the multi-language problem solution and it will rely on multi-site installations to provide each language from a different blog.
As an example the English version will be served by the blog at lang.dev, the Italian version from the blog at it.lang.dev and the French version from the blog at fr.lang.dev.
But how to tell the plugin that one blog is for the English version, one is for the Italian one and so on?
WordPress will, create, when setting up a multi-site installation, the blogs table containing the information about the blogs on the network; the last column in the table is aptly called lang_id:

The WordPress blogs table

The codex entry for the get_blog_details function states:

lang_id - (integer) ID of the language this blog is written in.

I will leverage that column to keep track of the various blog languages; that column will store an integer value that will map to language code.
Language codes cannot be hardcoded though and the super-admin should be able to specify which blog in the network will serve which language; e.g. the blog with a blog_id of 1 will have a lang_id that will map to en, the blog with blog_id 2 will have a lang_id mapping to it and so on.
To allow the super-admin to set those information I’ve set up a basic network options page and the only markup I’m printing on the page is a nonce field.
A dedicated service provider (it comes from the DI52 package) will hook the page:

<?php

namespace Tongues\ServiceProviders;


class NetworkAdminPage extends \tad_DI52_ServiceProvider {

  /**
   * Binds and sets up implementations.
   */
  public function register() {
    $this->container->bind( 'Tongues\\UI\\Admin\\NetworkOptionsInterface', 'Tongues\\UI\\Admin\\NetworkOptions' );

    $container = $this->container;

    add_action( 'network_admin_menu', function () use ( $container ) {
      $title = __( 'Tongues', 'tongues' );
      add_submenu_page(
        'settings.php',
         $title,
         $title,
         'manage_network',
         'tongues-network-options', 
         [ $container->make( 'Tongues\\UI\\Admin\\NetworkOptionsInterface'), 'render'  ]
      );
    } );
  }

  /**
   * Binds and sets up implementations at boot time.
   */
  public function boot() {
    // TODO: Implement boot() method.
  }
}

and the page class itself will deal with the concise printing.

<?php

namespace Tongues\UI\Admin;


class NetworkOptionsInterfaceInterface 
  extends AbstractOptionsPageInterface
  implements OptionsPageInterface, NetworkOptionsPageInterface {

  protected $nonceAction = 'tongues-network-options';

  public function render() {
    wp_nonce_field( $this->getNonceAction(), $this->getNonceField() );
  }
}

The functional and acceptance tests difference

The page in its current state is, as one might guess, empty.

It contains but a fundamental piece of code: the nonce field. This will allow me to send authenticated HTTP requests to the backend and a functional test does not need any UI to be in place to be written.
Forms and their components are merely a way to assemble the structured data needed to make a meaningful request.
Was I to write an acceptance test I would have needed an actual UI to interact with but a functional test will not need that.
Right now, provided the receiving end was in place, I could write a test like this:

<?php
namespace REST;

use FunctionalTester;

class NetworkOptionsCest {

  /**
   * @test
   * it should mark bad request if trying to update lang_id for non existing blog_id
   */
  public function it_should_mark_bad_request_if_trying_to_update_lang_id_for_non_existing_blog_id( FunctionalTester $I ) {
    $I->loginAsAdmin();
    $I->amOnAdminPage( '/network/settings.php?page=tongues-network-options' );
    $I->haveHttpHeader( 'X-WP-Nonce', $I->grabValueFrom( 'input[name="tongues-nonce"]' ) );
    $I->sendAjaxPostRequest( '/wp-json/tongues/v1/network-config', [
      'lang_ids' => [ 'blog_id' => 21, 'lang_id' => 23 ]
    ] );

    $I->canSeeResponseCodeIs( '400' );
  }
}

Next

I will implement the REST API endpoints and code needed to handle the first requests and have the first functional tests passing.