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.