Score:1

How do I mock \Drupal::httpClient()?

jp flag

I'm unit testing a utility library (of our own making) that makes calls to an external REST API with \Drupal::httpClient()

So, I have a library class with static functions:

class myUtils {
  public static function getFromApi($path)
  {
    ...
    $response = \Drupal::httpClient()->request( ... );
    ...
  }

...

}

and I want to call that from a test class:

class myUtilsTest extends \Codeception\Test\Unit
{
  // Lots of other stuff ...
  public function testGetFromApi()
  {
     // Do some mocking magic
     ...
     myUtils::getFromApi('/some/test/path');
     ...
  }
}

I realize I can make a \GuzzleHttp\Handler\MockHandler and set it up to return whatever I want it to, but how do I set it to "overwrite" the call to \Drupal->httpClient()?

I've seen some examples which seem to assume you have an instance of a class that has its own httpClient member, and this is easily mocked - but in my use case, there's no reason at all to design the utilities like that. So how do I mock the global \Drupal::httpClient() in this case?

Thanks in advance for any response.

Kevin avatar
in flag
Easier to just include a test only module that has a middleware service to provide responses for any internal or external API. Otherwise you have to mock out the class that is injected to the service you are testing and mock each method that is in use in your implementation.
in flag
This is exactly the situation why you need dependency injection, so that the APIs you depend are loosely coupled to your code and can be mocked and swapped out during testing. Once you start depending on things like HTTP API, the database API, the entity API, and friends, you might want to consider putting your tools a service class instead. I would reserve static functions to standalone logic (e.g. core's Html.php, Xss.php, UrlHelper.php, etc are good examples).
jp flag
OK, thanks, will consider it! However, having to change a perfectly valid architecture & implementation solely for testing purposes rubs me the wrong way a bit. But yes, it might be a way.
Kevin avatar
in flag
Using the static container in classes is discouraged for several reasons, this is one of them. Fortunately, it is not too hard to do DI.
jbarrio avatar
cn flag
I agree with Patrick and trying to contribute to this in a positive way I'd say that you just found something to improve in your current implementation. And possibly the reason you are facing this issue is due to the fact that you didn't implement your solution with a T(B)DD approach in mind from the beginning. Next time this will not happen to you again :_)
jp flag
I suppose you're all right! My own testing experience is mainly from Python where it's possible to mock somewhat more dynamically, so that the actual code design doesn't have to consider testing (since you can always test). This, of course, is Drupal and PHP and not Python and Django, where my main experience lies. :-)
Score:3
ph flag

Converting your class to use dependency injection would look something like this:

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use GuzzleHttp\Client

class myUtils implements ContainerFactoryPluginInterface {

  /**
   * The http service.
   *
   * @var \GuzzleHttp\Client
   */
  protected $httpClient;

  final public function __construct(array $configuration, $plugin_id, $plugin_definition, Client $httpClient) {
    $this->httpClient = $httpClient;
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('http_client')
    );
  }

  public function getFromApi($path)
  {
    ...
    $response = $this->httpClient->request( ... );
    ...
  }

...

}
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.