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 description:
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());
-    }
-}