If you write automated tests for your PHP code (and you should try!) you’ll often find yourself needing to mock/stub calls to pieces of code that are not under test.

With WordPress, our unit tests tend to be more akin to integration/functional tests where we’re testing that several pieces of interrelated code work well when exercised in the whole. As a result of this, it’s quite common that the code under test may call out to third party services or other parts of the codebase that do not produce deterministic results.

Code needing to mock WP_Http

One example of non-determinism in our code are HTTP requests.

This is because we are unlikely to be in control of the response from the requested URL and this will therefore produce potentially different results on every invocation of the code.

For me, recent experience of this came in the form of the work I did to add a new REST API endpoint within the Gutenberg Plugin which allows a developers to retrieve information from a remote URL (such as the contents of the <title> tag). In order to do this the code makes a HTTP call to the remote URL and processes the response to use within the API endpoint.

As you can imagine, when placing this system under test, the major source of non-determinism was the response returned from the HTTP request to the remote URL. I was making the call using wp_remote_get which is a utility function which delegates its responsibility to the underlying WP_Http API.

Mocking WP_Http responses

To test code like this we need to make it deterministic, which is to say that we need to mock out the response we received from the HTTP request in order to return a known result across all our test runs.

The best way I’ve found to do this is to use the pre_http_request filter which resides within the request method of the WordPress WP_Http class.

As explained in the documentation, this filter allows us to filter the preemptive return value of an HTTP request.

Returning a non-false value from the filter will short-circuit the HTTP request and return early with that value.

https://developer.wordpress.org/reference/hooks/pre_http_request/#description

What this means, is that any non-falsey (and valid) value we return from the filter will short-circuit the execution of the WP_Http request and cause it [the known value] to be returned as the value of the response. This ensures that the actual HTTP request is never made and enables us to return a known value which is entirely under our control.

We can employ this technique within our automated tests to ensure we have deterministic results.

How to mock HTTP requests in WordPress unit tests

Let’s an example an from the REST API endpoint I mentioned above.

public function test_get_items() {

    wp_set_current_user( self::$admin_id );

    $request = new WP_REST_Request( 'GET', '__experimental/url-details' );
    $request->set_query_params(
        array(
            'url' => 'https://google.com' // not in our control
        )
    );
    $response = rest_get_server()->dispatch( $request );
    $data     = $response->get_data();

    $this->assertEquals(
        array(
            'title' => '' // how can we reliably assert here?
        ),
        $data
    );
}

In the example above, we are making a RESTful request to the API endpoint at __experimental/url-details. This endpoint makes a HTTP request to the remote URL passed to the endpoint as the url parameter. Currently, as we are not fully in control of the website at https://google.com there is no way for us to reliably perform an assertion on the expected result of our test run.

In order to achieve this we first need to filter the pre_http_request hook to return known values:

public function test_get_items() {

    // Hook in and return a known response
    add_filter( 'pre_http_request', function() {
        return array(
            'headers'     => array(),
            'cookies'     => array(),
            'filename'    => null,
            'response'    => 200,
            'status_code' => 200,
            'success'     => 1,
            'body'        => file_get_contents( __DIR__ . '/fixtures/example-website.html' ) : '',
        );
    }, 10, 3 );

    // Rest of method here...
}

You will note we can fully control everything about the response including the status_code and the body. In this case we are reading in a simple fixture file which contains a basic HTML webpage to use as our body.

With this in place we can now adjust our test to assert on the known response values:

// Note the <title> comes from the fixture HTML returned by
// the filter `pre_http_request`.
$this->assertEquals(
    array(
        'title' => 'Example Website &mdash; - with encoded content.',
    ),
    $data
);

Putting this all together we have a test which reliably exercises the functionality of the REST API endpoint, without reying on non-deterministic input (eg: a response from remote URL).

public function test_get_items() {

    // Hook in and return a known response
    add_filter( 'pre_http_request', function() {
        return array(
            'headers'     => array(),
            'cookies'     => array(),
            'filename'    => null,
            'response'    => 200,
            'status_code' => 200,
            'success'     => 1,
            'body'        => file_get_contents( __DIR__ . '/fixtures/example-website.html' ) : '',
        );
    }, 10, 3 );

    

    wp_set_current_user( self::$admin_id );

    $request = new WP_REST_Request( 'GET', '__experimental/url-details' );
    $request->set_query_params(
        array(
            'url' => 'https://google.com' // not in our control
        )
    );
    $response = rest_get_server()->dispatch( $request );
    $data     = $response->get_data();

    // Note the <title> comes from the fixture HTML returned by
    // the filter `pre_http_request`.
    $this->assertEquals(
        array(
            'title' => 'Example Website &mdash; - with encoded content.',
        ),
        $data
    );
}

Of course, the code above is largely illustrative. You might find it helpful to see the source code for the tests which run against the real API endpoint.

This technique will allow you to mock out any HTTP requests you make within your unit tests so long as you are using one of the functions that form part of the WP_Http API, including:

…and more. I’ve found it to be extremely handy for writing PHPUnit tests for WordPress code.

If you’ve found this useful, or you have a better technique then let me know in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.