Testing Custom League OAuth2 Client Providers

This week I had to create a custom league/oauth2-client provider to talk to an private OAuth 2 server (also courtesy of The PHP League).

Most of this is pretty routine. Implement some getters that are used in a few template methods. Most of those getters are public and easy to test, but a few of the required methods deal with HTTP responses. I was a bit stuck on how to test them. Which brings us to this post!

Under the hood, league/oauth2-client uses Guzzle and Guzzle has some tooling for testing. Testing a custom OAuth 2 provider is about combining the two.

Example: Testing an Access Token Response

In this example the custom provider create a custom access token class. It would be good to make sure that createAccessToken is invoked correctly from the public getAccessToken method.

use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Grant\AbstractGrant;

class CustomProvider extneds AbstractProvider
{
    // ...

    protected function createAccessToken(array $response, AbstractGrant $grant) : CustomAccessToken
    {
        return new CustomAccessToken($response);
    }
}

To test this we’ll set up a Guzzle MockHandler and use it to back a guzzle client. MockHandler is a stub implementation of Guzzle’s usual handler. It returns pre-programmed responses. An OAuth 2 server response to an access token request with a JSON body with a few keys (access_token and expires_in for example).

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\Psr7\Response;

$response = new Response(200, [
    'Content-Type' => 'application/json',
], json_encode([
    'access_token' => 'testToken',
    // may also want to provide these keys depending on your use case
    'resfresh_token' => 'testRefreshToken',
    'expires_in' => 3600,
]));

$handler = new MockHandler([$response]);
$handlerStack = HandlerStack::create($handler);

$httpClient = new HttpClient([
    'handler' => $handlerStack,
]);

Now we need to pass that handler to the custom provider. The base AbstractProvider provides a $collaborators argument to its constructor, so that’s one option.

$provider = new CustomProvider([
  // options here
], [
  // collaborators
  'httpClient' => $httpClient,
]);

There’s also a setHttpClient method:

$provider = new CustomProvider(/* ... */);

$provider->setHttpClient($httpClient);

Ones the client is set on the custom provider, invoking getAccessToken will use that stub response and a test can verify that the createAccessToken method is being called as expected.

$token = $provider->getAccessToken('authorization_code', [
    'code' => 'testAuthCode',
]);

assert($token instanceof CustomAccessToken);
assert($token->getToken === 'testToken');  // make sure the mock response was used

Should you wish to inspect the requests that were make, Guzzle provides a history middleware. I don’t think this is necessary however. It’s the responsiblity of the league/oauth2-client package to verify that the outgoing requests are correct. Most custom providers only need to worry that they are correctly dealing with responses.

A similar strategy here can be used to verify the other methods that deal with response (checkResponse and createResourceOwner for example.

Why Not Just Mock/Spy a HTTP Client?

I try really hard to avoid mocking code I don’t own. By using Guzzle’s Mock handler I can ensure that I’m using guzzle (via the custom provider) correctly. This is someone of a personal choice. I don’t think mocking an http client or event mocking part of the provider is necessarily bad. But using Guzzle’s built in testing facilities made the most sense to me.

That said, this method will break should league/oauth2-client ever change its underlying http client. So would mocking the http client directly.