Repository: thephpleague/oauth2-client (php)

Issue:
Allow any psr-18 http client (instead of requiring guzzle)
```bash
composer why guzzlehttp/guzzle
league/oauth2-client 2.7.0 requires guzzlehttp/guzzle (^6.0 || ^7.0) 
```

I prefer to use the Symfony HttpClient, but this package requires guzzle.  Any chance it could be more generic, that is, require any PSR-18 client to operate, then the user can install guzzle or whatever they want.

Thanks for your consideration.  Not urgent, but it'd be nice to have one less dependency.





Pull request:
Replace GuzzleHttp with PSR-18 HTTP Clients
This pull request replaces the Guzzle HTTP Client with PSR 17 and 18 interfaces. Closes #1019.

#### Changes to Dependencies:

* The dependencies `psr/http-client`, `psr/http-factory`, and `psr/http-message` were added to the `require` section. 
* The `guzzlehttp/guzzle` package was moved to the `require-dev` section.

#### Changes to the AbstractProvider Class:

* Guzzle's `HttpClientInterface` was replaced with `ClientInterface` from the PSR-18 standard. 
* Guzzle's `RequestFactory` was replaced with `RequestFactoryInterface` and `StreamFactoryInterface` from the PSR-17 standard.

#### Removal of the RequestFactory Class:

* The entire `RequestFactory` class was removed as it's no longer needed with the introduction of the PSR-17 standard interfaces.


Patch:
diff --git a/composer.json b/composer.json
index f6773fc..69c0a6d 100644
--- a/composer.json
+++ b/composer.json
@@ -8,9 +8,12 @@
     "require": {
         "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
         "ext-json": "*",
-        "guzzlehttp/guzzle": "^7.4.5"
+        "psr/http-client": "^1.0",
+        "psr/http-factory": "^1.1",
+        "psr/http-message": "^2.0"
     },
     "require-dev": {
+        "guzzlehttp/guzzle": "^7.4.5",
         "mockery/mockery": "^1.6",
         "php-parallel-lint/php-parallel-lint": "^1.4",
         "phpunit/phpunit": "^10.5 || ^11.5",
diff --git a/src/Provider/AbstractProvider.php b/src/Provider/AbstractProvider.php
index 26f6d1c..9c11181 100644
--- a/src/Provider/AbstractProvider.php
+++ b/src/Provider/AbstractProvider.php
@@ -15,7 +15,6 @@
 namespace League\OAuth2\Client\Provider;
 
 use GuzzleHttp\Client as HttpClient;
-use GuzzleHttp\ClientInterface as HttpClientInterface;
 use GuzzleHttp\Exception\BadResponseException;
 use InvalidArgumentException;
 use League\OAuth2\Client\Grant\AbstractGrant;
@@ -28,9 +27,11 @@ use League\OAuth2\Client\Token\AccessTokenInterface;
 use League\OAuth2\Client\Tool\ArrayAccessorTrait;
 use League\OAuth2\Client\Tool\GuardedPropertyTrait;
 use League\OAuth2\Client\Tool\QueryBuilderTrait;
-use League\OAuth2\Client\Tool\RequestFactory;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
 use Psr\Http\Message\RequestInterface;
 use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamFactoryInterface;
 use UnexpectedValueException;
 
 /**
@@ -103,12 +104,17 @@ abstract class AbstractProvider
     protected $grantFactory;
 
     /**
-     * @var RequestFactory
+     * @var RequestFactoryInterface
      */
     protected $requestFactory;
 
     /**
-     * @var HttpClientInterface
+     * @var StreamFactoryInterface
+     */
+    protected $streamFactory;
+
+    /**
+     * @var ClientInterface
      */
     protected $httpClient;
 
@@ -140,16 +146,17 @@ abstract class AbstractProvider
         $this->setGrantFactory($collaborators['grantFactory']);
 
         if (empty($collaborators['requestFactory'])) {
-            $collaborators['requestFactory'] = new RequestFactory();
+            throw new InvalidArgumentException('No request factory set');
         }
         $this->setRequestFactory($collaborators['requestFactory']);
 
-        if (empty($collaborators['httpClient'])) {
-            $client_options = $this->getAllowedClientOptions($options);
+        if (empty($collaborators['streamFactory'])) {
+            throw new InvalidArgumentException('No stream factory set');
+        }
+        $this->setStreamFactory($collaborators['streamFactory']);
 
-            $collaborators['httpClient'] = new HttpClient(
-                array_intersect_key($options, array_flip($client_options))
-            );
+        if (empty($collaborators['httpClient'])) {
+            throw new InvalidArgumentException('No http client set');
         }
         $this->setHttpClient($collaborators['httpClient']);
 
@@ -205,10 +212,10 @@ abstract class AbstractProvider
     /**
      * Sets the request factory instance.
      *
-     * @param  RequestFactory $factory
+     * @param  RequestFactoryInterface $factory
      * @return self
      */
-    public function setRequestFactory(RequestFactory $factory)
+    public function setRequestFactory(RequestFactoryInterface $factory)
     {
         $this->requestFactory = $factory;
 
@@ -218,20 +225,43 @@ abstract class AbstractProvider
     /**
      * Returns the request factory instance.
      *
-     * @return RequestFactory
+     * @return RequestFactoryInterface
      */
     public function getRequestFactory()
     {
         return $this->requestFactory;
     }
 
+    /**
+     * Sets the stream factory instance.
+     *
+     * @param  StreamFactoryInterface $factory
+     * @return self
+     */
+    public function setStreamFactory(StreamFactoryInterface $factory)
+    {
+        $this->streamFactory = $factory;
+
+        return $this;
+    }
+
+    /**
+     * Returns the stream factory instance.
+     *
+     * @return StreamFactoryInterface
+     */
+    public function getStreamFactory()
+    {
+        return $this->streamFactory;
+    }
+
     /**
      * Sets the HTTP client instance.
      *
-     * @param  HttpClientInterface $client
+     * @param  ClientInterface $client
      * @return self
      */
-    public function setHttpClient(HttpClientInterface $client)
+    public function setHttpClient(ClientInterface $client)
     {
         $this->httpClient = $client;
 
@@ -241,7 +271,7 @@ abstract class AbstractProvider
     /**
      * Returns the HTTP client instance.
      *
-     * @return HttpClientInterface
+     * @return ClientInterface
      */
     public function getHttpClient()
     {
@@ -696,9 +726,23 @@ abstract class AbstractProvider
         ];
 
         $options = array_merge_recursive($defaults, $options);
-        $factory = $this->getRequestFactory();
+        $requestFactory = $this->getRequestFactory();
+        $streamFactory = $this->getStreamFactory();
+
+        $request = $requestFactory->createRequest($method, $url);
+        foreach ($options['headers'] as $name => $value) {
+            $request = $request->withAddedHeader($name, $value);
+        }
+
+        $request = $request->withProtocolVersion($options['version'] ?? '1.1');
+
+        if (!empty($options['body'])) {
+            $request = $request->withBody(
+                $streamFactory->createStream($options['body'] ?? null)
+            );
+        }
 
-        return $factory->getRequestWithOptions($method, $url, $options);
+        return $request;
     }
 
     /**
@@ -712,7 +756,7 @@ abstract class AbstractProvider
      */
     public function getResponse(RequestInterface $request)
     {
-        return $this->getHttpClient()->send($request);
+        return $this->getHttpClient()->sendRequest($request);
     }
 
     /**
diff --git a/src/Tool/RequestFactory.php b/src/Tool/RequestFactory.php
deleted file mode 100644
index 1af4342..0000000
--- a/src/Tool/RequestFactory.php
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-/**
- * This file is part of the league/oauth2-client library
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- *
- * @copyright Copyright (c) Alex Bilbie <hello@alexbilbie.com>
- * @license http://opensource.org/licenses/MIT MIT
- * @link http://thephpleague.com/oauth2-client/ Documentation
- * @link https://packagist.org/packages/league/oauth2-client Packagist
- * @link https://github.com/thephpleague/oauth2-client GitHub
- */
-
-namespace League\OAuth2\Client\Tool;
-
-use GuzzleHttp\Psr7\Request;
-
-/**
- * Used to produce PSR-7 Request instances.
- *
- * @link https://github.com/guzzle/guzzle/pull/1101
- */
-class RequestFactory
-{
-    /**
-     * Creates a PSR-7 Request instance.
-     *
-     * @param  null|string $method HTTP method for the request.
-     * @param  null|string $uri URI for the request.
-     * @param  array $headers Headers for the message.
-     * @param  string|resource|StreamInterface $body Message body.
-     * @param  string $version HTTP protocol version.
-     *
-     * @return Request
-     */
-    public function getRequest(
-        $method,
-        $uri,
-        array $headers = [],
-        $body = null,
-        $version = '1.1'
-    ) {
-        return new Request($method, $uri, $headers, $body, $version);
-    }
-
-    /**
-     * Parses simplified options.
-     *
-     * @param array $options Simplified options.
-     *
-     * @return array Extended options for use with getRequest.
-     */
-    protected function parseOptions(array $options)
-    {
-        // Should match default values for getRequest
-        $defaults = [
-            'headers' => [],
-            'body'    => null,
-            'version' => '1.1',
-        ];
-
-        return array_merge($defaults, $options);
-    }
-
-    /**
-     * Creates a request using a simplified array of options.
-     *
-     * @param  null|string $method
-     * @param  null|string $uri
-     * @param  array $options
-     *
-     * @return Request
-     */
-    public function getRequestWithOptions($method, $uri, array $options = [])
-    {
-        $options = $this->parseOptions($options);
-
-        return $this->getRequest(
-            $method,
-            $uri,
-            $options['headers'],
-            $options['body'],
-            $options['version']
-        );
-    }
-}


Test patch:
diff --git a/test/src/Grant/GrantTestCase.php b/test/src/Grant/GrantTestCase.php
index e09e22d..00249cc 100644
--- a/test/src/Grant/GrantTestCase.php
+++ b/test/src/Grant/GrantTestCase.php
@@ -2,11 +2,13 @@
 
 namespace League\OAuth2\Client\Test\Grant;
 
-use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Client;
+use GuzzleHttp\Psr7\HttpFactory;
 use Mockery;
 use Mockery\MockInterface;
 use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
+use Psr\Http\Client\ClientInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\StreamInterface;
 use League\OAuth2\Client\Token\AccessTokenInterface;
@@ -20,6 +22,10 @@ abstract class GrantTestCase extends TestCase
             'clientId' => 'mock_client_id',
             'clientSecret' => 'mock_secret',
             'redirectUri' => 'none',
+        ],[
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
     }
 
@@ -72,7 +78,7 @@ abstract class GrantTestCase extends TestCase
         /** @var ClientInterface & MockInterface $client */
         $client = Mockery::spy(ClientInterface::class)->makePartial();
         $client
-            ->shouldReceive('send')
+            ->shouldReceive('sendRequest')
             ->once()
             ->withArgs(function ($request) {
                 parse_str((string) $request->getBody(), $body);
diff --git a/test/src/Provider/AbstractProviderTest.php b/test/src/Provider/AbstractProviderTest.php
index 842e1fe..f388c0e 100644
--- a/test/src/Provider/AbstractProviderTest.php
+++ b/test/src/Provider/AbstractProviderTest.php
@@ -2,13 +2,15 @@
 
 namespace League\OAuth2\Client\Test\Provider;
 
+use InvalidArgumentException;
 use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider;
 use Mockery;
 use PHPUnit\Framework\Attributes\DataProvider;
 use ReflectionClass;
 use UnexpectedValueException;
 use GuzzleHttp\Exception\BadResponseException;
-use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Client;
+use GuzzleHttp\Psr7\HttpFactory;
 use League\OAuth2\Client\Provider\AbstractProvider;
 use League\OAuth2\Client\Test\Provider\Fake as MockProvider;
 use League\OAuth2\Client\Grant\AbstractGrant;
@@ -16,11 +18,13 @@ use League\OAuth2\Client\Grant\GrantFactory;
 use League\OAuth2\Client\Grant\Exception\InvalidGrantException;
 use League\OAuth2\Client\Token\AccessToken;
 use League\OAuth2\Client\Token\AccessTokenInterface;
-use League\OAuth2\Client\Tool\RequestFactory;
 use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
 use PHPUnit\Framework\TestCase;
+use Psr\Http\Client\ClientInterface;
 use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\RequestFactoryInterface;
 use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\StreamFactoryInterface;
 use Psr\Http\Message\StreamInterface;
 
 class AbstractProviderTest extends TestCase
@@ -31,6 +35,10 @@ class AbstractProviderTest extends TestCase
             'clientId' => 'mock_client_id',
             'clientSecret' => 'mock_secret',
             'redirectUri' => 'none',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
     }
 
@@ -55,6 +63,38 @@ class AbstractProviderTest extends TestCase
         $this->getMockProvider()->getAccessToken($grant, ['invalid_parameter' => 'none']);
     }
 
+    public function testMissingRequestFactory()
+    {
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('No request factory set');
+        $provider = new Fake();
+    }
+
+    public function testMissingStreamFactory()
+    {
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('No stream factory set');
+        $provider = new Fake(
+            [],
+            [
+                'requestFactory' => new HttpFactory()
+            ]
+        );
+    }
+
+    public function testMissingHttpClient()
+    {
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage('No http client set');
+        $provider = new Fake(
+            [],
+            [
+                'requestFactory' => new HttpFactory(),
+                'streamFactory' => new HttpFactory()
+            ]
+        );
+    }
+
     public function testAuthorizationUrlStateParam()
     {
         $authUrl = $this->getMockProvider()->getAuthorizationUrl([
@@ -91,62 +131,27 @@ class AbstractProviderTest extends TestCase
             'redirectUri' => 'http://example.org/redirect'
         ];
 
-        $mockProvider = new MockProvider($options);
+        $mockProvider = new MockProvider($options, [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
 
         $this->assertSame($options['clientId'], $mockProvider->getClientId());
         $this->assertSame($options['clientSecret'], $mockProvider->getClientSecret());
         $this->assertSame($options['redirectUri'], $mockProvider->getRedirectUri());
     }
 
-    public function testConstructorSetsClientOptions()
-    {
-        $timeout = rand(100, 900);
-
-        $mockProvider = new MockProvider(compact('timeout'));
-
-        $config = $mockProvider->getHttpClient()->getConfig();
-
-        $this->assertArrayHasKey('timeout', $config);
-        $this->assertEquals($timeout, $config['timeout']);
-    }
-
-    public function testCanSetAProxy()
-    {
-        $proxy = '192.168.0.1:8888';
-
-        $mockProvider = new MockProvider(['proxy' => $proxy]);
-
-        $config = $mockProvider->getHttpClient()->getConfig();
-
-        $this->assertArrayHasKey('proxy', $config);
-        $this->assertEquals($proxy, $config['proxy']);
-    }
-
-    public function testCannotDisableVerifyIfNoProxy()
-    {
-        $mockProvider = new MockProvider(['verify' => false]);
-
-        $config = $mockProvider->getHttpClient()->getConfig();
-
-        $this->assertArrayHasKey('verify', $config);
-        $this->assertTrue($config['verify']);
-    }
-
-    public function testCanDisableVerificationIfThereIsAProxy()
-    {
-        $mockProvider = new MockProvider(['proxy' => '192.168.0.1:8888', 'verify' => false]);
-
-        $config = $mockProvider->getHttpClient()->getConfig();
-
-        $this->assertArrayHasKey('verify', $config);
-        $this->assertFalse($config['verify']);
-    }
-
     public function testConstructorSetsGrantFactory()
     {
         $mockAdapter = Mockery::mock(GrantFactory::class);
 
-        $mockProvider = new MockProvider([], ['grantFactory' => $mockAdapter]);
+        $mockProvider = new MockProvider([], [
+            'grantFactory' => $mockAdapter,
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
         $this->assertSame($mockAdapter, $mockProvider->getGrantFactory());
     }
 
@@ -154,18 +159,38 @@ class AbstractProviderTest extends TestCase
     {
         $mockAdapter = Mockery::mock(ClientInterface::class);
 
-        $mockProvider = new MockProvider([], ['httpClient' => $mockAdapter]);
+        $mockProvider = new MockProvider([], [
+            'httpClient' => $mockAdapter,
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
         $this->assertSame($mockAdapter, $mockProvider->getHttpClient());
     }
 
     public function testConstructorSetsRequestFactory()
     {
-        $mockAdapter = Mockery::mock(RequestFactory::class);
+        $mockAdapter = Mockery::mock(RequestFactoryInterface::class);
 
-        $mockProvider = new MockProvider([], ['requestFactory' => $mockAdapter]);
+        $mockProvider = new MockProvider([], [
+            'httpClient' => new Client(),
+            'requestFactory' => $mockAdapter,
+            'streamFactory' => new HttpFactory()
+        ]);
         $this->assertSame($mockAdapter, $mockProvider->getRequestFactory());
     }
 
+    public function testConstructorSetsStreamFactory()
+    {
+        $mockAdapter = Mockery::mock(StreamFactoryInterface::class);
+
+        $mockProvider = new MockProvider([], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => $mockAdapter
+        ]);
+        $this->assertSame($mockAdapter, $mockProvider->getStreamFactory());
+    }
+
     public function testSetRedirectHandler()
     {
         $testFunction = false;
@@ -192,6 +217,10 @@ class AbstractProviderTest extends TestCase
             'clientId' => 'mock_client_id',
             'clientSecret' => 'mock_secret',
             'redirectUri' => 'none',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         $token = new AccessToken(['access_token' => 'abc', 'expires_in' => 3600]);
@@ -219,7 +248,7 @@ class AbstractProviderTest extends TestCase
 
 
         $client = Mockery::spy(ClientInterface::class, [
-            'send' => $response,
+            'sendRequest' => $response,
         ]);
 
         $provider->setHttpClient($client);
@@ -234,7 +263,7 @@ class AbstractProviderTest extends TestCase
         $this->assertArrayHasKey('email', $user->toArray());
 
         $client
-            ->shouldHaveReceived('send')
+            ->shouldHaveReceived('sendRequest')
             ->once()
             ->withArgs(function ($request) use ($url) {
                 return $request->getMethod() === 'GET'
@@ -249,6 +278,10 @@ class AbstractProviderTest extends TestCase
             'clientId' => 'mock_client_id',
             'clientSecret' => 'mock_secret',
             'redirectUri' => 'none',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         $token = new AccessToken(['access_token' => 'abc', 'expires_in' => 3600]);
@@ -267,7 +300,7 @@ class AbstractProviderTest extends TestCase
             ->andReturn(['text/html']);
 
         $client = Mockery::mock(ClientInterface::class, [
-            'send' => $response,
+            'sendRequest' => $response,
         ]);
 
         $provider->setHttpClient($client);
@@ -384,7 +417,7 @@ class AbstractProviderTest extends TestCase
             ->andReturn(['application/json']);
 
         $client = Mockery::spy(ClientInterface::class, [
-            'send' => $response,
+            'sendRequest' => $response,
         ]);
         $provider->setHttpClient($client);
 
@@ -394,7 +427,7 @@ class AbstractProviderTest extends TestCase
         $provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
 
         $client
-            ->shouldHaveReceived('send')
+            ->shouldHaveReceived('sendRequest')
             ->once()
             ->withArgs(function ($request) use ($pkceCode) {
                 parse_str((string)$request->getBody(), $body);
@@ -457,6 +490,10 @@ class AbstractProviderTest extends TestCase
             'clientId' => 'mock_client_id',
             'clientSecret' => 'mock_secret',
             'redirectUri' => 'none',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         $error = ["error" => "Foo error", "code" => 1337];
@@ -480,7 +517,7 @@ class AbstractProviderTest extends TestCase
             ->andReturn(['application/json']);
 
         $client = Mockery::spy(ClientInterface::class, [
-            'send' => $response,
+            'sendRequest' => $response,
         ]);
 
         $provider->setHttpClient($client);
@@ -505,7 +542,7 @@ class AbstractProviderTest extends TestCase
         $this->assertEquals($error, $errorBody);
 
         $client
-            ->shouldHaveReceived('send')
+            ->shouldHaveReceived('sendRequest')
             ->once()
             ->withArgs(function ($request) use ($method, $url) {
                 return $request->getMethod() === $method
@@ -520,6 +557,10 @@ class AbstractProviderTest extends TestCase
             'clientId' => 'mock_client_id',
             'clientSecret' => 'mock_secret',
             'redirectUri' => 'none',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         $stream = Mockery::mock(StreamInterface::class, [
@@ -539,7 +580,7 @@ class AbstractProviderTest extends TestCase
 
         $client = Mockery::mock(ClientInterface::class);
         $client
-            ->shouldReceive('send')
+            ->shouldReceive('sendRequest')
             ->andThrow(new BadResponseException('test exception', $request, $response));
 
         $provider->setHttpClient($client);
@@ -548,14 +589,18 @@ class AbstractProviderTest extends TestCase
 
     public function testGetResponse()
     {
-        $provider = new MockProvider();
+        $provider = new MockProvider([], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
 
         $request = Mockery::mock(RequestInterface::class);
         $response = Mockery::mock(ResponseInterface::class);
 
         $client = Mockery::mock(ClientInterface::class);
         $client
-            ->shouldReceive('send')
+            ->shouldReceive('sendRequest')
             ->with($request)
             ->andReturn($response);
 
@@ -567,7 +612,11 @@ class AbstractProviderTest extends TestCase
 
     public function testAuthenticatedRequestAndResponse()
     {
-        $provider = new MockProvider();
+        $provider = new MockProvider([], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
 
         $token = new AccessToken(['access_token' => 'abc', 'expires_in' => 3600]);
         $request = $provider->getAuthenticatedRequest('get', 'https://api.example.com/v1/test', $token);
@@ -586,7 +635,7 @@ class AbstractProviderTest extends TestCase
 
         $client = Mockery::mock(ClientInterface::class);
         $client
-            ->shouldReceive('send')
+            ->shouldReceive('sendRequest')
             ->with($request)
             ->andReturn($response);
 
@@ -620,6 +669,10 @@ class AbstractProviderTest extends TestCase
             'clientId' => 'mock_client_id',
             'clientSecret' => 'mock_secret',
             'redirectUri' => 'none',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         $provider->setAccessTokenMethod($method);
@@ -654,7 +707,7 @@ class AbstractProviderTest extends TestCase
             ->andReturn(['application/json']);
 
         $client = Mockery::spy(ClientInterface::class, [
-            'send' => $response,
+            'sendRequest' => $response,
         ]);
 
         $provider->setHttpClient($client);
@@ -667,7 +720,7 @@ class AbstractProviderTest extends TestCase
         $this->assertSame($raw_response['expires'], $token->getExpires());
 
         $client
-            ->shouldHaveReceived('send')
+            ->shouldHaveReceived('sendRequest')
             ->once()
             ->withArgs(function ($request) use ($provider) {
                 return $request->getMethod() === $provider->getAccessTokenMethod()
@@ -693,7 +746,7 @@ class AbstractProviderTest extends TestCase
             ->andReturn(['text/plain']);
 
         $client = Mockery::mock(ClientInterface::class, [
-            'send' => $response,
+            'sendRequest' => $response,
         ]);
 
         $provider->setHttpClient($client);
@@ -832,7 +885,11 @@ class AbstractProviderTest extends TestCase
             'guarded' => 'foo'
         ];
 
-        $provider = new Fake\ProviderWithGuardedProperties($options);
+        $provider = new Fake\ProviderWithGuardedProperties($options, [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
 
         $this->assertNotEquals(
             $options['skipMeDuringMassAssignment'],
@@ -916,6 +973,10 @@ class AbstractProviderTest extends TestCase
             'urlAuthorize' => 'https://example.com/authorize',
             'urlAccessToken' => 'https://example.com/accessToken',
             'urlResourceOwnerDetails' => 'https://api.example.com/owner',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         $reflectedProvider = new \ReflectionObject($provider);
diff --git a/test/src/Provider/Generic.php b/test/src/Provider/Generic.php
index 9701de3..002e360 100644
--- a/test/src/Provider/Generic.php
+++ b/test/src/Provider/Generic.php
@@ -16,7 +16,7 @@ class Generic extends GenericProvider
             'redirectUri'  => 'none',
         ];
 
-        parent::__construct($options);
+        parent::__construct($options, $collaborators);
     }
 
     protected function fetchResourceOwnerDetails(AccessToken $token)
diff --git a/test/src/Provider/GenericProviderTest.php b/test/src/Provider/GenericProviderTest.php
index 52ce2bc..01e577f 100644
--- a/test/src/Provider/GenericProviderTest.php
+++ b/test/src/Provider/GenericProviderTest.php
@@ -3,6 +3,8 @@
 namespace League\OAuth2\Client\Test\Provider;
 
 use InvalidArgumentException;
+use GuzzleHttp\Client;
+use GuzzleHttp\Psr7\HttpFactory;
 use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
 use League\OAuth2\Client\Test\Provider\Generic as MockProvider;
 use League\OAuth2\Client\Provider\GenericProvider;
@@ -39,7 +41,10 @@ class GenericProviderTest extends TestCase
             }
         }
 
-        $provider = new GenericProvider($required + [
+        $provider = new GenericProvider($required, [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
     }
 
@@ -63,6 +68,10 @@ class GenericProviderTest extends TestCase
             'clientId'       => 'mock_client_id',
             'clientSecret'   => 'mock_secret',
             'redirectUri'    => 'none',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         foreach ($options as $key => $expected) {
@@ -105,6 +114,10 @@ class GenericProviderTest extends TestCase
             'urlAccessToken' => 'http://example.com/token',
             'urlResourceOwnerDetails' => 'http://example.com/user',
             'responseResourceOwnerId' => 'mock_response_uid',
+        ], [
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
         ]);
 
         $user = $provider->getResourceOwner($token);
@@ -130,7 +143,11 @@ class GenericProviderTest extends TestCase
             'urlResourceOwnerDetails' => 'http://example.com/user',
         ];
 
-        $provider = new GenericProvider($options);
+        $provider = new GenericProvider($options,[
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
 
         $reflection = new ReflectionClass(get_class($provider));
 
@@ -157,7 +174,11 @@ class GenericProviderTest extends TestCase
             'urlResourceOwnerDetails' => 'http://example.com/user',
         ];
 
-        $provider = new GenericProvider($options + $extraOptions);
+        $provider = new GenericProvider($options + $extraOptions,[
+            'httpClient' => new Client(),
+            'requestFactory' => new HttpFactory(),
+            'streamFactory' => new HttpFactory()
+        ]);
 
         $reflection = new ReflectionClass(get_class($provider));
 
diff --git a/test/src/Tool/RequestFactoryTest.php b/test/src/Tool/RequestFactoryTest.php
deleted file mode 100644
index 8a9dbc7..0000000
--- a/test/src/Tool/RequestFactoryTest.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-namespace League\OAuth2\Client\Test\Tool;
-
-use League\OAuth2\Client\Tool\RequestFactory;
-use PHPUnit\Framework\TestCase;
-use Psr\Http\Message\RequestInterface;
-
-class RequestFactoryTest extends TestCase
-{
-    public function testGetRequest()
-    {
-        $method  = 'get';
-        $uri     = '/test';
-
-        $factory = new RequestFactory();
-        $request = $factory->getRequest($method, $uri);
-
-        $this->assertInstanceOf(RequestInterface::class, $request);
-        $this->assertSame(strtoupper($method), $request->getMethod());
-        $this->assertSame($uri, (string) $request->getUri());
-
-        $headers         = ['X-Test' => 'Foo'];
-        $body            = 'test body';
-        $protocolVersion = '1.0';
-
-        $request = $factory->getRequest($method, $uri, $headers, $body, $protocolVersion);
-
-        $this->assertTrue($request->hasHeader('X-Test'));
-        $this->assertSame($body, (string) $request->getBody());
-        $this->assertSame($protocolVersion, $request->getProtocolVersion());
-    }
-
-    public function testGetRequestWithOptions()
-    {
-        $method  = 'head';
-        $uri     = '/test/options';
-
-        $factory = new RequestFactory();
-        $request = $factory->getRequestWithOptions($method, $uri);
-
-        $this->assertInstanceOf(RequestInterface::class, $request);
-        $this->assertSame(strtoupper($method), $request->getMethod());
-        $this->assertSame($uri, (string) $request->getUri());
-
-        $options = [
-            'body'    => 'another=test&form=body',
-            'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
-        ];
-
-        $request = $factory->getRequestWithOptions($method, $uri, $options);
-
-        $this->assertContains($options['headers']['Content-Type'], $request->getHeader('Content-Type'));
-        $this->assertSame($options['body'], (string) $request->getBody());
-    }
-}