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>
WordPress Plugin Ajax Unit Tests - ajax test files

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 extends WP_Ajax_UnitTestCase and contains tests for Ajax methods.

  • test-ajax-others.php - defines class Test_Sos_Ajax_others class which extends WP_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.