How do I mock HTTP requests for PHPUnit?

I’m writing a plugin that makes requests to the Facebook graph API. As I don’t want my unit tests to actually make these requests, how would I overcome this? My method calls both wp_remote_get and wp_remote_post. Searching there does seem to be a way to mock functions using runkit, here

I want to avoid having contributers requiring too many dependencies so would like to avoid the above method. Is there any other options? My class extends the WP_UnitTestCase so I’m hoping maybe there’s something from the wp unit-tests that I could use?

Related posts

5 comments

  1. If you take a look at WP_HTTP->request() (which all related functions wrap) it provides a filter hook for the purpose of overriding making a request in favor of returning arbitrary data as response:

    // Allow plugins to short-circuit the request
    $pre = apply_filters( 'pre_http_request', false, $r, $url );
    if ( false !== $pre )
        return $pre;
    
  2. In one (two) word(s): Mock Data. PHPUnit got getMock() available exactly for that. As the other answers already have perfectly summoned…

    • where to get valid mock data (Otto) – make sure you occasionally fetch fresh data
    • and where to intercept the data (Rarst)
    • and what tools to use (TomJNowell)

    …there only (might be) the issue left that your local SSL certificate verification might fail. WP ships with a filter for that:

    add_filter( 'https_local_ssl_verify', '__return_false' );
    

    For any further information about the WP HTTP API, you might want to dig into this answer, this answer, this answer by @Wyck and this answer by @toscho.

  3. Take the results you get from a valid or invalid request, serialize them into strings, then add code that unserializes the string back into the variable instead of doing the request.

  4. In order to isolate your code further, I would wrap the wp_remote_get etc calls in an interface with two implementations. One implementation calls wp_remote_get, and the other returns test data.

    Using a tool such as runkit in this situation sidesteps the actual problem you have, which is that your code and the APIs are too tightly coupled, and a level of encapsulation and abstraction would be beneficial.

  5. I’ve had to mock WordPress’s HTTP request functions on a few occasions, and so I decided to build a tool for doing that: WP HTTP TestCase

    Basically it provides an easy way to do the various things that the other answers have outlined. From the readme:

    WP HTTP Testcase

    PHPUnit testcase for testing code that uses WordPress’s WP_Http
    class.

    If you use wp_remote_request() or other wrappers for WP_Http
    methods in your code, this makes it difficult to test, especially if
    the remote server may not be reachable from your testing environment.
    This testcase solves this by letting you route your requests to a
    different different host address, use a cached set of responses, or
    just mock the remote responses by supplying artificial ones.

    Installation

    You can install this package using composer:

    composer require --dev jdgrimes/wp-http-testcase:~1.1

    Usage

    To use it in your code, you need to first include the
    wp-http-testcase.php file in your PHPUnit bootstrap file. If you
    will be using the host routing and response caching features, you will
    need to call WP_HTTP_TestCase::init() in your bootstrap file.

    Then, in your tests that involve WP_Http, you need to extend
    WP_HTTP_TestCase instead of WP_UnitTestCase as you normally would.

    Mocking Responses

    Using Response Caching

    The best way of testing, when possible, it to set up a mock host to
    handle the requests. In some cases, you may want or need to actually
    send the requests through to the real server, and that can be done as
    well. Which of these you do will depend on the nature of the requests,
    and what side-effects they produce on the recipient host.

    Setting Up a Test Host

    For example, if you are testing a plugin that makes requests to an API
    provided by another plugin or other software, you probably don’t want
    or need to test this on a live site. Instead, you can set up a test
    site, or use a local server that is part of your development
    environment. There you can install the software that handles the
    requests. Once this is done, you can run your tests against that test
    site like this:

    WP_HTTP_TC_HOST=localhost phpunit

    Just replace localhost with the hostname of the local server. Note
    that the WP_HTTP_TC_* flags can be defined as PHP constants, or as
    bash environment variables as above. The latter will take precedence.

    Enabling Caching

    Of course this will be much slower than most other unit tests, because
    the requests are bound to take a bit of time. That is where caching
    comes in. When caching is enabled, the response to each request is
    cached the first time it is run, and the cached version is used in the
    future. This means that your tests can remain lightning fast.

    To enable caching, just add this to your bootstrap:

    define( 'WP_HTTP_TC_USE_CACHING', true );

    You’ll probably also want to specify the directory to save the cache
    in, via WP_HTTP_TC_CACHE_DIR. You can utilize multiple cache groups
    and switch between them using WP_HTTP_TC_CACHE_GROUP.

    Using the Live Host

    There is the second case though, where you are unable to set up a test
    server. An example where this would be the case would be if your
    plugin makes requests to the API provided by GitHub. Depending on the
    situation, it may be feasible to actually make the requests to the
    “live” recipient. The main issue again is that the requests will make
    the tests take a long time to complete. There is also the possibility
    that the API isn’t always accessible from your testing environment, or
    that your tests will end up pounding the API too hard and you’ll get
    blocked. This is where caching can help you. You only need to run your
    tests against the “live” API once in a while, and the rest of the time
    you can test using the cached responses.

    Supplying Artificial Responses

    Of course, there may be times when it isn’t possible to create a test
    server, and it isn’t feasible to run against the live server either.
    In this case, you may want to hard-code artificial responses into your
    tests. Here is how you can do that:

    Before calling the code that will invoke the HTTP request, you need to
    set the function to mock the responses like so:

    $this->http_responder = array( $this, 'mock_server_response' );

    The HTTP responder function will be passed two arguments, the request
    arguments and the URL the request was intended for.

    protected function mock_server_response( $request, $url ) {
       return array( 'body' => 'Test response.' ); 
    }
    

    For a full list of the $request and response arguments, see
    WP_Http::request()

Comments are closed.