12.5. WordPress Advanced Unit Tests
In the previous blog we saw tests that uses WordPress API functions to investigate the outcome of a method. However, such WordPress functions which are helpful to write tests are few, and to test majority of plugin methods, we need to rely on WordPress global variables and classes. Many WordPress functions store information in global variables or in classes which is used later to construct the output i.e. response sent to the browser. In unit tests, it is easy to fetch the data from the global variables or classes for tests.
In this blog, we explain test methods which rely on WordPress globals and classes, and also, show an easy way to find out the global variables used by such WordPress functions.
But before that, we touch upon another category of tests that simulate WordPress admin screen.
Admin Screen
The main file of the plugin, share-on-social.php, is coded in such way that when plugin is loaded, it includes front end php files. However, for admin screen along with front end files, it also includes admin php file. As front end files are loaded both for site screens as well for admin screen, we can test their inclusion easily. But, we need to simulate admin screen to test admin files inclusion.
WordPress uses class
WP_Screen to
define the screen and a global variable current_screen holds the
current WP_Screen
object. Normally, when a test runs the
current_screen is null which indicates that tests are are executed in
normal site screens. To switch to Admin screen, we have to create a new
WP_Screen object and assign it to current_screen.
The trimmed version of Test_Share_On_Social::test_setup_for_admin()
shows how it is done.
share-on-social/tests/phpunit/tests/test-share-on-social.php
public function test_setup_for_admin () {
global $current_screen;
$this->assertNull( $current_screen );
$included_files = get_included_files();
$file = SOS_PATH . 'admin/class-admin.php';
$this->assertNotContains( $file, $included_files );
$this->assertFalse( has_action( 'plugins_loaded', 'load_sos_admin' ) );
// setup admin screen to make is_admin() as true
$screen = WP_Screen::get( 'admin_init' );
$current_screen = $screen;
// now call setup
setup_sos_plugin();
// test for admin stuff
$included_files = get_included_files();
$file = SOS_PATH . 'admin/class-admin.php';
$this->assertContains( $file, $included_files );
$this->assertTrue(
has_action( 'plugins_loaded', 'load_sos_admin' ) == true );
// revert back the screen
$current_screen = null;
}
To start with, we assert that global current_screen is null which confirms that we are in site screen. Next, with PHP function get_included_files(), we get list of loaded files and assert that admin files are not loaded as we are in site screen at this stage.
Next, we obtain an instance of admin screen with the static method
WP_Screen::get()
. The parameter admin_init is the hook name to
which the screen is related. The returned screen object is assigned to
global current_screen and with this, we are now effectively in admin
screen and the WordPress function is_admin() function returns true.
Next, we call setup_sos_plugin()
and as is_admin() returns true, it
loads admin files in addition to the front end files. We test whether
admin files are included and also, whether function load_sos_admin()
is hooked to action plugin_loaded. Once tests are over, we revert
back to site screen by assigning the current_screen to null.
We use WP_Screen also in some test methods in Test_Help
and
Test_Sos
test cases.
WordPress Global Variables and Classes.
More often than not, WordPress functions use variety of global arrays to hold data of menu items, actions, scripts etc., which are subsequently used to build the response that is sent to the browser. Similarly, some functions use class objects such as WP_Screen, WP_Scripts etc., to hold data which again are accessed through global variables. Methods which use such functions can be tested by accessing the global arrays and class objects.
Test Localized Scripts
In Sos_Frontend::add_sos_script()
, apart from enqueue, we also
localize script data with the help of WordPress function
wp_localize_script().
WordPress sends the localized data to the browser which is used by the
scripts on client side.
share-on-social/frontend/class-frontend.php
function add_sos_scripts () {
....
wp_localize_script( 'sos_script', 'sos_data',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'sos-save-stat' ),
'gplus_client_id' => $gplus_client_id
) );
}
Since WordPress doesn’t provide any function to access the localized
data, we test them using WordPress class WP_Scripts
.
share-on-social/tests/phpunit/tests/test-frontend.php
public function test_localized_script () {
global $wp_scripts;
....
// add shortcode and localize data
$this->frontend->setup();
$this->frontend->add_sos_scripts();
$data = $wp_scripts->get_data( 'sos_script', 'data' );
$data = substr( $data, strpos( $data, '{' ) - 1, strpos( $data, '}' ) + 1 );
$sos_data = json_decode( $data, true );
$this->assertCount( 3, $sos_data );
$this->assertarrayHasKey( 'ajax_url', $sos_data );
$this->assertarrayHasKey( 'nonce', $sos_data );
$this->assertarrayHasKey( 'gplus_client_id', $sos_data );
$this->assertSame( $sos_data<a href="http://codex.wordpress.org/Function_Reference/wp_verify_nonce" target="_blank"> 'ajax_url' ],
'http://localhost/wp-admin/admin-ajax.php' );
$this->assertSame( $sos_data[ 'gplus_client_id' ], 'test_gplusclientid' );
// wp_verify_nonce returns 1 or 2 when valid else false
$this->assertSame( 1,wp_verify_nonce( $sos_data[ 'nonce' ], 'sos-save-stat' ) );
}
WP_Script::get_data()
returns the localized data as JSON string and
PHP function json_decode() is used to decode which returns the array of
localized data. Out of the localized data, nonce is a special case as
its value is random. WordPress function
[wp_verify_nonce()
is used to test whether nonce is valid.
Test Custom Post Type
The method Sos::create_post_type()
defined in
share-on-social/admin/class-sos.php
registers a custom post type and
the test uses global array $wp_post_types
to test it.
share-on-social/tests/phpunit/tests/test-sos.php
public function test_create_post_type () {
global $wp_post_types;
if ( isset( $wp_post_types[ 'sos' ] ) ) {
unset( $wp_post_types[ 'sos' ] );
}
$this->assertarrayNotHasKey( 'sos', $wp_post_types );
// register post type
$this->sos->create_post_type();
$this->assertarrayHasKey( 'sos', $wp_post_types );
$sos_type = $wp_post_types[ 'sos' ];
$this->assertFalse( $sos_type->public );
$this->assertFalse( $sos_type->hierarchical );
$this->assertTrue( $sos_type->exclude_from_search );
$this->assertFalse( $sos_type->publicly_queryable );
$this->assertTrue( $sos_type->has_archive );
$this->assertSame( 15, $sos_type->menu_position );
$this->assertTrue( $sos_type->show_ui );
$this->assertTrue( $sos_type->show_in_menu );
$this->assertTrue( $sos_type->show_in_admin_bar );
$this->assertFalse( $sos_type->show_in_nav_menus );
$this->assertSame( SOS_URL . '/images/sos-icon.png',
$sos_type->menu_icon );
}
Test Menu and Sub Menu
Method Sos_Options::register_settings_page()
creates sub menu for the
options page and WordPress holds sub menu data in global array
$submenu
.
share-on-social/tests/phpunit/tests/test-options.php
public function test_register_settings_page () {
global $submenu;
$this->assertFalse( isset( $submenu[ 'edit.php?post_type=sos' ] ) );
$this->assertFalse(
Util::has_action( 'admin_page_sos_settings_page',
$this->sos_options, 'render_settings_page' ) );
$this->sos_options->register_settings_page();
$this->assertTrue( isset( $submenu[ 'edit.php?post_type=sos' ] ) );
$settings = $submenu[ 'edit.php?post_type=sos' ];
$this->assertSame( 'Settings', $settings[ 0 ][ 0 ] );
$this->assertSame( 'administrator', $settings[ 0 ][ 1 ] );
$this->assertSame( 'sos_settings_page', $settings[ 0 ][ 2 ] );
$this->assertSame( 'Common Options', $settings[ 0 ][ 3 ] );
$this->assertTrue(
Util::has_action( 'admin_page_sos_settings_page',
$this->sos_options, 'render_settings_page' ) );
}
Throughout the Share on Social Plugin test suit you will find many such tests that uses WordPress global arrays and class objects.
Find Global Variable and Class
When you write tests extensively then it is important to learn a technique to find out whether a WordPress function uses global array or class object. Let’s explain this with an example.
Method Sos_Options::register_settings_page()
uses WordPress function
add_submenu_page()
. To know, whether this function uses any global
array, open the function reference page
http://codex.wordpress.org/Function_Reference/add_submenu_page and
scroll down to Source File section where you will find a link to the
source file of the function.
Open the source file and search for function add_submenu_page() and there you gather that it uses global $submenu and $menu among other globals. When function uses multiple globals, you may have to study the source little bit to understand which of them holds the functions' outcome.
Once you know the target global variable, you can echo it in the test method to know more about data held by it. As globals are typically array or class object. use print_r() function to output the data.
Bear in mind, some of these arrays and objects are huge in size. It is much easier to analyze the output if data is piped to some file.
In the next blog, we explain the unit tests for internationalization functions.