Problem/Motivation
Sometimes we need a custom HTTP client for some custom services, usually for testing purposes. For example, to provide mocked responses for outgoing HTTP requests in functional tests.
For those cases, Drupal provides http_client_factory
service, which can provide http_client
with custom options.
But in tests, we usually need to provide a custom HTTP Client, not the original Guzzle class. And we can't do dependency injection here and replace the default class Drupal\Core\Http\ClientFactory
with a custom one because it doesn't implement any interface.
Steps to reproduce
1. Create a service that calls some external HTTP API, using an HTTP client from the factory, with custom options.
2. Try to create a test module for functional tests, which overrides the client factory and provides a custom HTTP Client with mocked responses.
You will get an error like this:
TypeError: Argument #1 ($httpClientFactory) must be of type Drupal\Core\Http\ClientFactory, Drupal\my_module_test\TestClientFactory given.
Proposed resolution
The optimal way to resolve this issue is to make Drupal\Core\Http\ClientFactoryInterface
and use it as the param type in the service constructors, instead of the Drupal Core class.
With this, we can easily create a custom factory, that implements this interface, and replace the core factory with a custom one.
Also, there is a workaround possible like this:
/**
* MyModule constructor.
*
* @param \Drupal\Core\Http\ClientFactory|\Drupal\my_module_test\TestClientFactory $httpClientFactory
* The HTTP client factory service.
*/
public function __construct(
protected \Drupal\Core\Http\ClientFactory|\Drupal\my_module_test\TestClientFactory $httpClientFactory,
) {
}
But this looks pretty ugly because we should not mention test assets in the main code.
So, with the suggested change we can type just an interface like this:
/**
* MyModule constructor.
*
* @param \Drupal\Core\Http\ClientFactoryInterface $httpClientFactory
* The HTTP client factory service.
*/
public function __construct(
protected \Drupal\Core\Http\ClientFactoryInterface $httpClientFactory,
) {
}
What do you think about this idea?