TruUcde Rabbithole 10/12 : Setup and Integration Tests (Part 1)

posted in: Development, Series, Testing, truucde, Wordpress | 0
Lynn Greyling: CC0 Public Domain

Okay, following on the previous articles in the series, particularly article 9 on installing and configuring the WordPress testing scaffold, in this article we’ll start by taking care of some set up items and testing to see if our plugin is properly loading into WordPress. We’ll follow up in Part 2 with the remaining integration tests.

One of the resources I found invaluable in getting going on this type of testing is CodeTab’s WP Plugin Development series https://www.codetab.org/tutorial/wordpress-plugin-development/introduction/. While the setup in this article series of the WP Unit Testing Framework (remember, WordPress calls it “Unit” testing but it is really “Integration” testing) predates the use of WP-CLI for installation, there is loads in the way of valuable advice for setup and testing approaches. I’ll be leaning on it quite a bit.

This type of thing happens quite often, you find a valuable resource that was written against previous versions or methods of the thing you are interested in. This does not diminish their value, in fact, it pushes you to understand their approach in their context and then ‘translate’ it for your own context.

Additions to the bootstrap file

The first valuable resource from CodeTab is a class of utility functions that address common needs. The class itself is valuable and licensing enables me to use in my testing. I always like to attribute the usage of code from others both in discussions about it and in the actual code using code comments.

We’ll first create a util directory inside of our integrationTests directory to house this file. This is common practice and keeps utility classes separate from our tests and enables us to exclude the folder from coverage reporting like so (lines 26-28):

<?xml version="1.0" encoding="UTF-8"?>
<!-- vendor/bin/phpunit -c phpunit_integration.xml -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
         bootstrap="./tests/integrationTests/bootstrap_integration.php"
         backupGlobals="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
>

    <php>
        <const name="WP_TESTS_MULTISITE" value="1" />
    </php>

    <testsuites>
        <testsuite name="truucde-int">
            <directory>tests/integrationTests/tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist addUncoveredFilesFromWhitelist="true">
            <file>truucde.php</file>
            <exclude>
                <directory>tests/integrationTests/util</directory>
            </exclude>
        </whitelist>
    </filter>

    <logging>
        <log type="coverage-html" target="./tests/integrationTests/coverage" showUncoveredFiles="true"/>
    </logging>
</phpunit>

We’ll load this class in the bootstrap_integration.php file below our existing content on lines 33-34 (see previous article for current state of this file and further down this article for a completed version.)

// Add test utility functions.
require_once 'util/class-util.php';

We’ll add a few more things to the bootstrap file while we are in here. You may have noticed in the previous article that while we got our test WordPress firing up properly that there was no mention of the first, or default user. When creating a WordPress site the conventional way, the person setting up the site usually winds up being the first network administration or superadmin with a user ID of 1. We need to do that ourselves with this test scaffold. Arguably it could be done in our test files instead, but philosophically I want to begin my test file with the standard WordPress multisite installation conditions, so I’ll take care of this in the bootstrap file as follows (lines 36-38):

$current_user = new WP_User( 1 ); // 1 here refers to the user id
$current_user->set_role( 'administrator' );
grant_super_admin(1);

This last thing and the previous I cribbed from someone, once I find it again I’ll attribute properly. This is a good lesson in putting attributes right in the code. This last item is a set of echo statements that prints a line in our console which tells us where the test WordPress installation is located (lines 40 – 42):

echo PHP_EOL;
echo 'Using WordPress core : ' . ABSPATH . PHP_EOL;
echo PHP_EOL;

Here’s the output of the previous echo statement:

Using WordPress core : /var/folders/_n/rft3ncgj1r165v0rx7wxtkj1_q1v8f/T/wordpress//

And the completed bootstrap_integration.php file in its entirety:

<?php
/**
 * PHPUnit bootstrap file
 *
 * @package Truucde_Blog
 */

$_tests_dir = getenv( 'WP_TESTS_DIR' );

if ( ! $_tests_dir ) {
	$_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
}

if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) {
	echo "Could not find $_tests_dir/includes/functions.php, have you run bin/install-wp-tests.sh ?" . PHP_EOL; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	exit( 1 );
}

// Give access to tests_add_filter() function.
require_once $_tests_dir . '/includes/functions.php';

/**
 * Manually load the plugin being tested.
 */
function _manually_load_plugin() {
	require dirname( dirname( dirname( __FILE__ ) ) ) . '/truucde.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

// Start up the WP testing environment.
require $_tests_dir . '/includes/bootstrap.php';

// Add test utility functions.
require_once 'util/class-util.php';

$current_user = new WP_User( 1 );
$current_user->set_role( 'administrator' );
grant_super_admin(1);

echo PHP_EOL;
echo 'Using WordPress core : ' . ABSPATH . PHP_EOL;
echo PHP_EOL;

The Integration Test file

The great thing about writing all this up is it gives me a second chance to review what I did, why I did and if it makes sense. The WordPress Scaffold Test environment is new to me so there is a strong likelihood that I’ll need to revisit my thinking. In fact, I know of one case for sure. But this is an advantage of testing, we get to know our own code even better. So, in this article and part 2 in the next article, we’ll focus on the integration test file and plugin code file as originally written. Anything we find that needs revisiting we’ll fix up in a further refactoring article.

Okay, we’ll build up the test file bit by bit, loosely following the same order as the unit test file. You may want to review the unit test article and the first article in this series to familiarize yourself with the code and tests.

Note: line numbers in the snippets to follow will not match the line numbers in the completed file, but will ease discussion and reduce article length.

Okay, we start with our namespace definition and a couple of use statements that identify key requirements of test file.

<?php
namespace TruUcdeBlog;

use WP_Error;
use WP_UnitTestCase;

Then comes our class declaration and some class variables (attributes).

class MsTruUcdeTest extends WP_UnitTestCase {

	/*
	 * Define all the things
	 */
	public $site2_admin;
	public $site3_admin;
	public $blog_2;
	public $blog_3;

	public $Target_code  = 'user_email';
	public $Black_msg    = 'You cannot use that email address to signup. We are having problems with them blocking some of our email. Please use another email provider.';
	public $White_msg    = 'Sorry, that email address is not allowed!';
	public $Target_data  = 'User email things.';
	public $Another_code = 'user_foolishness';
	public $Another_msg  = 'The eagle lands at midnight';
	public $Another_data = 'Spy games';

The first thing to notice is that here our test class extends WP_UnitTestCase which gives us access to all its goodies. WP_UnitTestCase in its declaration extends PHPUnit so we have access to its goodies as well. Remember this, it will come up.

The first set of attribute declarations (lines 4 to 7) establish variables for two additional users, we have a super admin user already. To test our plugin we need a non-super admin users who can be site admins or other site users. Note, we haven’t actually defined the users yet, we are just creating convenience variables to hold them when we do. I’m calling them site2_admin and site3_admin to associate them with the blogs they’ll be admins of.

The second set are convenience variables similar to those we used in the unit tests in a previous article. They all hold strings of text that are replaced with shorter variables. Note that the variables are named in such a way that the logic of our code will be more understandable than using the strings themselves.

Also, as identified in the first article of this series, the WP_Error object works on strings, and the logic of our plugin is dependant on strings. The string wording could easily change as WordPress is updated, and certainly the strings will be different for languages other than English. Using variables for these at the top of our file makes it easy to use different strings as may be necessary.

Test setup() & teardown()

We saw setup() and teardown() class methods in our unit test file in a previous article. Most testing frameworks include them for doing things before testing and then cleaning up those things after testing. Our pair of methods here are as follows:

	/*
	 * Test setup
	 */
	public function setUp() {
		parent::setUp();

		// add additional subsites.
		$this->blog_2 = $this->factory->blog->create();
		$this->blog_3 = $this->factory->blog->create();

		// add additional users: user 1 - superadmin already created.
		$this->site2_admin = $this->factory->user->create();
		$this->site3_admin = $this->factory->user->create();

		// add users to blogs with roles.
		add_user_to_blog( $this->blog_2, $this->site2_admin, 'administrator' );
		add_user_to_blog( $this->blog_3, $this->site2_admin, 'editor' );
		add_user_to_blog( $this->blog_2, $this->site3_admin, 'editor' );
		add_user_to_blog( $this->blog_3, $this->site3_admin, 'administrator' );
	}

	/*
	 * Test Teardown
	 */
	public function tearDown() {
		parent::tearDown();
	}

Okay, the first items of note are the parent::setUp() and parent::tearDown() calls (line 5 and 26). By using these statements we call the WP_UnitTestCase routines in addition to the things we need to do. These set up and clear out the state of the test WordPress instance when we are finished.

The remainder of the business is done in the setUp function. The comments are a give away, but let’s look at exactly what is happening here. It’s one of the best sets of features of the WP_UnitTestCase routines, factories. It is pretty easy to guess what is going on, we are using the blog factory to create a couple of blogs (or sub-sites) and the user factory to create a couple of users.

Each factory responds to three methods: create(), create_and_get() and create_count($count). There are several different factories including: attachments, blog, bookmark, comment, network, post, term and user. Documentation is a little thin on the interwebs, but various folks talk about them in their blogs and tutorials. Also, you can look more closely at the code here: https://core.trac.wordpress.org/browser/trunk/tests/phpunit/includes/factory. You will also have the code on your own drive with the rest of the WordPress testing stuff. Here’s a screen cap of the location on mine. (Remember the echo statements we added to the bootstrap file above? Have a look at my output, it is helpful in finding the files.)

We close our setup by adding our site2_admin and site3_admin to the additional blogs. We add them to each blog according to their blog/admin role and to the opposite blog with editor roles..

This is to reflect the nature of multisite WordPress where are a user may have admin positions on one or more blogs and lower-level permissions on other blogs. We’ll include checks for this in our tests.

Setup Test, Plugin Connection and Filter Registered

The next four tests are really the next four things we want to know.

  1. Is everything working
  2. Is our plugin actually hooked up
  3. Is our plugin properly registered on the right filter
  4. Are our blogs, users and roles working correctly.
	/**
	 * Setup test: basic sanity check
	 */
	public function testBasic() {
		$this->assertTrue( true );
	}

	/**
	 * Check for a plugin constant to ensure connection with plugin
	 */
	public function test_plugin_constant() {
		$this->assertSame( 'user_email', TARGET_ERROR_CODE );
	}

	/*
	 * Make sure filter is registered
	 */
	public function test_for_filter() {
		$this->assertEquals(
			10,
			has_filter( 'wpmu_validate_user_signup', 'TruUcdeBlog\on_loaded' )
		);

	}

	/*
	 * Check context, blogs, users, roles
	 */
	public function test_context() {
		global $current_user;
		global $current_blog;

		// Blog 2
		switch_to_blog( $this->blog_2 );
		set_current_screen( 'user_new.php' );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertTrue( user_can( $this->site2_admin, 'promote_users' ) );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'promote_users' ) );
		
		// Blog 3
		switch_to_blog( $this->blog_3 );
		set_current_screen( 'user_new.php' );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertTrue( user_can( $this->site3_admin, 'promote_users' ) );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'promote_users' ) );
	}

We’ve seen a function like testBasic() before in this series. It is a basic sanity test. We are now in the midst of very complicated stuff, a bunch of which is stored outside our project folder. It just makes good sense to build the sanity check right into the test file. That way if the first test every fails we know that our installation/setup is borq’d in someway (using a system cleanup utility to remove temporary files will do it).

The next test, test_plugin_constant() tests to see if a constant from our plugin is available and properly defined in our running WordPress context. I randomly chose TARGET_ERROR_CODE.

The third test, test_for_filter(), uses the WordPress core function has_filter() to check if our plugin is properly registered. has_filter() takes the filtername and optionally a function to check. If the function is specified has_filter() will return false if the function is not registered and will return the integer representing its priority if it is properly registered. So in this case for the test to be successful I need has_filter() to return 10 . We can see this as the final argument to the add_filter() command in the truucde_init() function of our plugin. (The plugin code can be found in the first article of this series in its entirety.)

/**
 * Hooking into WordPress
 */
function truucde_init() {
	add_filter( 'wpmu_validate_user_signup', 'TruUcdeBlog\on_loaded', 10 );
};

The fourth test, test_context(), checks that our additional users have the correct permissions on their respective blogs. We draw in global variables for user and blog. We then switch users and blogs as necessary and assert either true for false against the ‘promote_users’ capability as appropriate.

Wrapping up and Moving on

Okay, so in this article we looked at a number of setup and initialization items. We also looked at a few sanity tests to check that our plugin is properly hooked up. And we looked at a test for proper filter registration. This latter test essentially takes care of testing our truucde_init() function. We’ll carry on in the next article, by writing tests for our other three functions.

Comments are closed.