4

I'm trying to test a method that makes an HTTP request via Guzzle, so I can see if my code that filters that response is working. I have a mock json response in a json file that I want to use to test it. This is simply a saved version of an actual response.

I know you can mock methods e.g. this would override the get method to return a mock Guzzle Response. How do you mock a Guzzle response in Drupal 8?

    $this->mockHttp->expects($this->any())
      ->method('get')
      ->willReturn($mockGuzzleResponseShouldGoHere);

The mock Guzzle response needs to have a mock method getBody so I can do something like this:

$this->mockHttp->expects($this->any())
      ->method('getBody')
      ->willReturn(__DIR__ . 'Mocks/membership-classes.json');

and make sure that getBody returns my mock json file.

Method to test that makes a Http Guzzle request

<?php 

/**
   * Call the GetMembershipClasses service.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   */
  public function getMembershipClasses() {

    try {
      // Load configuration for this module.
      $response = $this->http_client->get(
        $this->getBaseUrl() . '/2014/06/membershipClasses', [
          'headers' => $this->getAnonymousHeaders(),
        ]);

        $response = json_decode($response->getBody());

        // Get to the meat of the response.
        $response = $response->GetMembershipClassesResponseMessage->MembershipClasses->MembershipClass;

        // Validate returned data.
        $filtered = array();
        foreach ($response as $membership_class) {
          $membership_class->MembershipClassCode = $this->removeSpaces($membership_class->MembershipClassCode);
          if ($membership_class->IsActive === TRUE) {
            $filtered[] = $membership_class;
          }
        }
        return new JsonResponse($filtered);
    }
    catch (RequestException $e) {
      return $this->handleError($e);
    }

  }

And the test class

<?php

/**
 * Class RouteControllerAnonymousTest.
 *
 */
class AnonymousWebServicesTest extends UnitTestCase {

  /**
   * Mock of http_client service.
   *
   * @var \GuzzleHttp\Client
   */
  protected $mockHttp;

  /**
   * Mock of state service.
   *
   * @var \Drupal\Core\State\State
   */
  protected $mockState;

  /**
   * Mock of config.factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactory
   */
  protected $mockConfigFactory;

  /**
   * Set up method which is called before any tests are
   * run. Mocks of the services that the class being tested rely on are
   * created.
   *
   * @return void
   */
  protected function setUp() {

    parent::setUp();
    $mockHttp = $this
      ->getMockBuilder('GuzzleHttp\Client')
      ->disableOriginalConstructor()
      ->setMethods(['get', 'getBody'])
      ->getMock();
    $this->mockHttp = $mockHttp;

    $mockState = $this
      ->getMockBuilder('Drupal\Core\State\State')
      ->disableOriginalConstructor()
      ->getMock();
    $this->mockState = $mockState;

    $mockConfigFactory = $this
      ->getMockBuilder('\Drupal\Core\Config\ConfigFactory')
      ->disableOriginalConstructor()
      ->getMock();
    $this->mockConfigFactory = $mockConfigFactory;

  }

  /**
   * Test the getMembershipClasses method.
   */
  public function testGetMembershipClasses() {

    // This needs to mock a Guzzle response from a get request
    $this->mockHttp->expects($this->any())
      ->method('get')
      ->willReturn('The mock guzzle response should go here);

    $this->mockHttp->expects($this->any())
      ->method('getBody')
      ->willReturn(__DIR__ . 'Mocks/membership-classes.json');

    $anonymousWebServices = new AnonymousWebServices(
      $this->mockHttp,
      $this->mockState,
      $this->mockConfigFactory
    );

    $membership_classes = $anonymousWebServices->getMembershipClasses();
    var_dump($membership_classes);
    // Insert some assert statement here

  }

}

3 Answers 3

10

Guzzle includes a mock handler that lets you provide a mock HTTP response. Docs are at http://docs.guzzlephp.org/en/latest/testing.html. Here's how you might use it in your unit test:

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

/**
 * Test the getMembershipClasses method.
 */
public function testGetMembershipClasses() {

  $body = file_get_contents(__DIR__ . '/Mocks/membership-class.json');
  // This sets up the mock client to respond to the first request it gets
  // with an HTTP 200 containing your mock json body.
  $mock = new MockHandler([
    new Response(200, [], $body),
  ]);
  $handler = HandlerStack::create($mock);
  $this->mockHttp = new HttpClient(['handler' => $handler]);

  $anonymousWebServices = new AnonymousWebServices(
    $this->mockHttp,
    $this->mockState,
    $this->mockConfigFactory
  );

  $membership_classes = $anonymousWebServices->getMembershipClasses();
  $membership_classes = json_decode($membership_classes->getContent());

  $this->assertNotEmpty($membership_classes);
  foreach ($membership_classes as $membership_class) {
    $this->assertTrue($membership_class->IsActive === TRUE);

  }

}

You can queue multiple mock responses by adding them to your handler at creation time:

  $mock = new MockHandler([
    new Response(200, [], $body),
    new Response(400, [], 'Request malformed.'),
  ]);

Or you can add them after your client has already been instantiated, as long as you keep a reference to your mock handler around. If you have multiple test methods, this is a lot easier to read and maintain:

public function setUp() {
  $this->mockHandler = new MockHandler();
  $handler = HandlerStack::create($this->mockHandler);
  $this->mockHttp = new HttpClient(['handler' => $handler]);
}

public function testSomething() {
  $this->mockHandler->append(new Response(200, [], 'apples and bananas'));
  $responseBody = $this->mockHttp->get('http://example.com')->getBody();
  $this->assertEquals($responseBody, 'apples and bananas');
}
0

So this might not be best practice but it does work. I will leave it open for a while if someone has a better answer but this is what I did:

Because the guzzle get() method should return a response object and the code that I'm testing uses the getBody() method of that object I created a mock class for this response with just one method - getBody() - which returns my mock data. I then pass an instance of the MockResponse class as the return value of my mock of the get() method from the mockHttp class.

So, there's probably a more elegant way but it does the job!

  /**
   * Test the getMembershipClasses method.
   */
  public function testGetMembershipClasses() {

    $mockResponse = new MockResponse(__DIR__ . '/Mocks/membership-class.json');

    $this->mockHttp->expects($this->any())
      ->method('get')
      ->willReturn($mockResponse);

    $anonymousWebServices = new AnonymousWebServices(
      $this->mockHttp,
      $this->mockState,
      $this->mockConfigFactory
    );

    $membership_classes = $anonymousWebServices->getMembershipClasses();
    $membership_classes = json_decode($membership_classes->getContent());

    $this->assertNotEmpty($membership_classes);
    foreach ($membership_classes as $membership_class) {
      $this->assertTrue($membership_class->IsActive === TRUE);

    }

  }

}

/**
 * Class MockResponse.
 *
 * @package Drupal\Tests\hambs_api_wrapper\Controller\Unit
 */
class MockResponse {

  private $filePath;

  /**
   * @param {string} $filePath
   *   - The file path where the mock test file lives.
   */
  public function __construct($filePath) {
    $this->filePath = $filePath;
  }

  /**
   * Mocking the Guzzle getBody() method.
   *
   * @return string
   */
  public function getBody() {

    $file_location = $this->filePath;
    $file_handler = fopen($file_location, "r");
    $file = fread($file_handler, filesize($file_location));
    fclose($file_handler);

    return $file;

  }

}
-1
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Exception\RequestException;

// Create a mock and queue two responses.
$mock = new MockHandler([
    new Response(200, ['X-Foo' => 'Bar'], 'Hello, World'),
    new Response(202, ['Content-Length' => 0]),
    new RequestException('Error Communicating with Server', new Request('GET', 'test'))
]);

$handlerStack = HandlerStack::create($mock);
$client = new Client(['handler' => $handlerStack]);

// The first request is intercepted with the first response.
$response = $client->request('GET', '/');
echo $response->getStatusCode();
//> 200
echo $response->getBody();
//> Hello, World
// The second request is intercepted with the second response.
echo $client->request('GET', '/')->getStatusCode();
//> 202

// Reset the queue and queue up a new response
$mock->reset();
$mock->append(new Response(201));

// As the mock was reset, the new response is the 201 CREATED,
// instead of the previously queued RequestException
echo $client->request('GET', '/')->getStatusCode();
//> 201

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.