12.7. WordPress Plugin Ajax Unit Tests
In the previous six blogs, we gone through the unit tests for the WordPress plugin regular methods. In this blog, we explore WordPress Plugin Ajax unit tests.
The normal test cases extend WP_UnitTestCase
. For Ajax unit tests,
WordPress Test Library comes with a separate class
WP_Ajax_UnitTestCase. Moreover, the construction of the test methods
are also bit different from the regular ones.
Ajax Test Case Group and Files
While normal test cases takes very little time to complete, WordPress Plugin Ajax tests are inherently very slow. Hence, it is not practical to run Ajax tests along with other tests.
Taking this into account, WordPress Tests uses PHPUnit annotation @group to group the Ajax tests. In phpunit.xml, use <exclude/> to exclude the Ajax test group during normal test run.
wp-simple-plugin/phpunit.xml
<phpunit bootstrap="tests/phpunit/bootstrap.php" backupGlobals="false"
colors="false" convertErrorsToExceptions="true"
convertNoticesToExceptions="true" convertWarningsToExceptions="true">
<testsuites>
<testsuite>
.... test files
</testsuite>
</testsuites>
<groups>
<exclude>
<group>ajax</group>
</exclude>
</groups>
</phpunit>
In Share on Social Plugin, the Ajax methods are defined in Sos_Ajax
class of share-on-social/admin/class-ajax.php
. But, this class also
contains some methods which are non Ajax and running them along with the
Ajax methods slows the test run further. To overcome that, the tests for
Sos_Ajax is split into two test files.
test-ajax.php - defines class
Test_Sos_Ajax
class which extendsWP_Ajax_UnitTestCase
and contains tests for Ajax methods.test-ajax-others.php - defines class
Test_Sos_Ajax_others
class which extendsWP_UnitTestCase
and contains tests for non Ajax methods of Sos_Ajax.
WordPress Ajax Test Case
Let’s go through Test_Sos_Ajax
to understand how Ajax Test Case is
defined.
share-on-social/tests/phpunit/tests/test-ajax.php
<?php
/**
* !!!! ONLY FOR AJAX CALL !!!!
*
* Tests for the Ajax calls to save and get sos stats.
* For speed, non ajax calls of class-ajax.php are tested in test-ajax-others.php
* Ajax tests are not marked risky when run in separate processes and wp_debug
* disabled. But, this makes tests slow so non ajax calls are kept separate
*
* @group ajax
* @runTestsInSeparateProcesses
*
*/
class Test_Sos_Ajax extends WP_Ajax_UnitTestCase {
var $sos_ajax;
public function setup () {
parent::setup();
require_once 'admin/class-ajax.php';
$this->sos_ajax = new Sos_Ajax();
wp_set_current_user( 1 );
}
public function teardown () {
parent::teardown();
unload_textdomain( 'sos-domain' );
}
....
}
For Ajax test case, in the header comments, we add two annotations. The annotation, @group ajax adds the test methods of this class to a group named ajax. The second annotation, @runTestsInSeparateProcesses tells PHPUnit to run each test from this class in a separate process.
The command to run normal and Ajax tests are as follows.
To Run NORMAL Tests
$ phpunit
To Run AJAX Tests
$ phpunit --group ajax
During normal run, Test_Sos_Ajax_Others is included with other test files of the plugin, however, in Ajax test run, only tests from Test_Sos_Ajax are executed as the class is annotated with @group ajax.
Risky Tests
PHPUnit marks some of the Ajax tests status as R. The tests are marked as Risky because they fail to close PHP buffer properly.
We couldn’t find out what exactly causes of this. Workaround to the buffer issue is to run Ajax tests in separate process. Tests are not marked as Risky when Ajax Test class is annotated with @runTestsInSeparateProcesses, though it drastically slows down the test run.
WordPress Ajax Tests
For Ajax, the test construction is quite different from the normal ones. Let’s go through some Ajax tests to understand how it is done.
The Ajax method Sos_Ajax::save_stats()
obtains data from PHP global
$_POST and saves the details in WordPress Option and returns 1 as
success flag.
share-on-social/admin/class-ajax.php
public function save_stats () {
$valid_req = check_ajax_referer( 'sos-save-stat', false, false );
if ( false == $valid_req ) {
wp_die( '-1' );
}
if ( ! isset( $_POST[ 'type' ] ) || ! isset( $_POST[ 'share_name' ] ) ) {
wp_die( '-1' );
}
$share_on = $_POST[ 'type' ];
$share_name = $_POST[ 'share_name' ];
$this->update_sos_stats( $share_on, $share_name );
$this->update_sos_stats_summary( $share_on, $share_name );
wp_die( '1' );
}
To test it, we use test method Test_Sos_Ajax::test_save_stat_stats()
.
share-on-social/tests/phpunit/tests/test-ajax.php
public function test_save_stat_stats () {
global $_POST;
$_POST[ 'type' ] = 'fb';
$_POST[ 'share_name' ] = 'test';
$_POST[ '_wpnonce' ] = wp_create_nonce( 'sos-save-stat' );
try {
$this->sos_ajax->setup();
$this->_handleAjax( 'save-stats' );
} catch (WPAjaxDieStopException $e) {}
$this->assertTrue( isset( $e ) );
$this->assertEquals( '1', $e->getMessage() );
$stats = get_option( 'sos_stats' );
$this->assertCount( 1, $stats );
$stat = $stats[ 0 ];
$this->assertSame( 'fb', $stat[ 'type' ] );
$this->assertSame( 'test', $stat[ 'share_name' ] );
}
After loading the data in $_POST, the test in a try-catch block, calls
Sos_Ajax::setup()
method which wires Ajax on server side as explained
in WordPress Ajax. Next, Ajax call is made using WP_Ajax_UnitTestCase::_handleAjax()
by passing
Ajax handler save-stats as the parameter. The exception
WPAjaxDieStopException
catches the outcome of Ajax call which is
assigned to variable $e
.
Ajax call return value is obtained from $e->getMessage() and tested whether call is successful. Since, Ajax method saves the stat in WordPress option, we obtain the data using WordPress function get_option() and test it.
The above Ajax method sends 1 or -1 as return value. But, there are
other types of Ajax method which returns data instead of a flag. One
such method is Sos_Ajax::get_stats()
which returns stats as JSON
string to the client.
share-on-social/tests/phpunit/tests/test-ajax.php
public function test_get_stats_single_stat () {
global $_POST;
$_POST[ '_wpnonce' ] = wp_create_nonce( 'sos-get-stats' );
$test_stats = $this->get_test_stats();
$stats[] = $test_stats[ 0 ];
add_option( 'sos_stats', $stats );
try {
$this->sos_ajax->setup();
$this->_handleAjax( 'get-stats' );
} catch (WPAjaxDieStopException $e) {}
$this->assertTrue( isset( $e ) );
$result = $e->getMessage();
$expected = '{"cols":[{"label":"Date","type":"date"},{"label":"FB","type":"number"}],"rows":[{"c":[{"v":"Date(2014,9,29)"},{"v":1}]}]}';
$this->assertSame( $expected, $result );
}
The test is similar to previous one, but here $e->getMessage()
returns
the JSON string returned by the Ajax call.
Exception Types and Die
To terminate Ajax methods, in Chapter WordPress Ajax, we suggested to use wp_die() instead of die() or exit(). Reason being, die() kills the test process even before the test completes, whereas wp_die() continues with test execution which allows WordPress to handle the output through a dedicated die handler.
WordPress Ajax method can end with four states.
Without output or return value.
With output or return value.
Error condition without return value.
Error condition with return value.
To handle these states, WP_Ajax_UnitTestCase supports two types of exception.
WPAjaxDieStopException
- for use with Ajax calls that returns or output data or status.WPAjaxDieContinueException
- for use with Ajax calls that never return data or status.
In Share on Social Plugin, we use wp_die()
to return data or status
and so, the plugins’ tests always use WPAjaxDieStopException
.
In the next blog, we explain the configuration and tests for WordPress Multisite.