aeris / guzzle-http-mock
A mock library for verifying requests made with the Guzzle Http Client, and mocking responses.
Installs: 12 952
Dependents: 2
Suggesters: 0
Security: 0
Stars: 27
Watchers: 14
Forks: 15
Open Issues: 2
Requires
- php: >=5.3.3
- guzzlehttp/guzzle: ~5.0.0
This package is not auto-updated.
Last update: 2024-09-28 16:41:30 UTC
README
A mock library for verifying requests made with the Guzzle Http Client, and mocking responses.
- Installation
- Overview
- Usage
Installation
Composer
You can install GuzzleHttpMock using composer:
php composer.phar require --dev aeris/guzzle-http-mock
Overview
GuzzleHttpMock allows you to setup Http request expectations, and mock responses.
// Create a guzzle http client $guzzleClient = new \GuzzleHttp\Client([ 'base_url' => 'http://www.example.com' ]); // Create a mock object, and start listening to guzzle client requests $httpMock = new \Aeris\GuzzleHttp\Mock(); $httpMock->attachToClient($guzzleClient); // Setup a request expectation $httpMock ->shouldReceiveRequest() ->withUrl('http://www.example.com/foo') ->withMethod('GET') ->withBodyParams([ 'foo' => 'bar' ]) ->andRespondWithJson([ 'faz', 'baz' ], $statusCode = 200); // Make a matching request $response = $guzzleClient->get('/foo', ['foo' => 'bar']); $response->json() == ['faz' => 'baz']; // true $response->getStatusCode() == 200; // true $httpMock->verify(); // all good. // Make an unexpected request $guzzleClient->post('/bar', ['faz' => 'baz']);; $httpMock->verify(); // UnexpectedHttpRequestException: Request does not match any expectation: // Request url does not match expected value. Actual: '/bar', Expected: '/foo' // Request body params does not match expected value. Actual: [ 'faz' => 'baz'], Expected: ['foo' => 'bar' ]
How does it work?
When a GuzzleHttpMock object is attached to the Guzzle Http client, it will intercept all requests made by the client. Whenever a request is made, the mock checks the request against set expectations, and sends a response to matching requests.
Calling $httpMock->verify()
checks that all expected requests have been made, and complains about any unexpected requests.
Usage
Attaching to a Guzzle Client
To start intercepting Http requests, the GuzzleHttpMock must be attached to a GuzzleClient:
// Create a guzzle http client $guzzleClient = new \GuzzleHttp\Client([ 'base_url' => 'http://www.example.com' ]); // Create a mock object, and start listening to guzzle client requests $httpMock = new \Aeris\GuzzleHttp\Mock(); $httpMock->attachToClient($guzzleClient);
Creating Request Expectations
The shouldReceiveRequest
method returns a \Aeris\GuzzleHttpMock\Expectation\RequestExpectation
object.
$requestExpectation = $httpMock->shouldReceiveRequest();
The RequestExpectation
object uses withXyz
methods to set expectations:
$requestExpectation->withUrl('http://www.example.com/foo');
The expectation setters are chainable, allowing for a fluid interface:
$httpMock ->shouldReceiveRequest() ->withUrl('http://www.example.com/foo') ->withMethod('POST');
Available Expectations
The following expectations are available on a \Aeris\GuzzleHttpMock\Expectation\RequestExpectation
object.
Default Expectations
By default, a request is expected to be made one time, with an Http method of 'GET'.
// So this: $httpMock ->shouldReceiveRequest() ->withUrl('http://www.example.com/foo'); // is the same as this: $httpMock ->shouldReceiveRequest() ->withUrl('http://www.example.com/foo') ->once() ->withMethod('GET');
Directly Setting an Expected Request
In addition to specifying request expectations individually, you can also directly set a \GuzzleHttp\Message\RequestInterface
object as an expectation.
$expectedRequest = $guzzleClient->createRequest([ 'PUT', 'http://www.example.com/foo', [ 'query' => ['faz' => 'baz'], 'body' => json_encode(['shazaam' => 'kablooey']), 'headers' => [ 'Content-Type' => 'application/json' ], ] ]); $httpClient->shouldReceiveRequest($expectedRequest);
Custom Expectations
All expectation methods accept either a value or a callable
as a parameter. By passing a callable, you can create custom expectations. For example:
$httpMock ->shouldReceiveRequest() ->withBodyParams(function($actualParams) { return $actualParams['foo'] === 'bar'; });
In this case, the expectation will fail if the actual request body has a foo
params which does not equal bar
.
GuzzleHttpMock provides some built-in custom expectations, as well. For example:
use Aeris\GuzzleHttpMock\Expect; $httpMock ->shouldReceiveRequest() // Check URL against a regex ->withUrl(new Expect\Matches('/^https:/')) // Check query params against an array ->withQueryParams(new Expect\ArrayEquals(['foo' => 'bar'])) // Allow any body params ->withBodyParams(new Expect\Any());
Mocking Responses
When a request is made which matches an expectation, the GuzzleHttpMock will intercept the request, and respond with a mock response.
$httpMock ->shouldReceiveRequest() ->withMethod('GET') ->withUrl('http://www.example.com/foo') ->andRespondWithJson(['foo' => 'bar']); $response = $guzzleClient->get('/foo'); $response->json() == ['foo' => 'bar']; // true
Available Responses
The following methods are available for mocking responses:
Directly Setting a Mock Response
You may mock a response directly using a response object:
$response = new \GuzzleHttp\Message\Response( b200, ['Content-Type' = 'application/json'], \GuzzleHttp\Streams\Stream::factory(json_encode(['foo' => 'bar' ]) ); // This is necessary to normalize the response // in a way that Guzzle expects. $messageFactory = \GuzzleHttp\Message\MessageFactory(); $response = $messageFactory->fromMessage($response); $httpMock ->shouldReceiveRequest() ->withMethod('GET') ->withUrl('http://www.example.com/foo') ->andResponseWith($response);
Verifying Expectations
Expectations may be verified using the \Aeris\GuzzleHttpMock::verify()
method.
$httpMock ->shouldReceiveRequest() ->withUrl('http://www.example.com/foo'); $guzzleClient->get('/bar'); $httpMock->verify(); // UnexpectedRequestException: Request does not match any expectation. // Request url does not match expected value. Actual: '/bar', Expected: '/foo'.
With PHPUnit
When using GuzzleHttpMock with PHPUnit, make sure to add Mock::verify()
to your teardown:
class MyUnitTest extends \PHPUnit_Framework_TestCase { private $guzzleClient; private $httpMock; public function setUp() { // Setup your guzzle client and mock $this->guzzleClient = new \GuzzleHttp\Client([ 'base_url' => 'http://www.example.com' ]); $this->httpMock = new \Aeris\GuzzleHttpMock(); $this->httpMock->attachToClient($this->guzzleClient); } public function tearDown() { // Make sure all request expectations are met. $this->httpMock->verify(); // Failed expectations will throw an \Aeris\GuzzleHttpMock\Exception\UnexpectedHttpRequestException } }
Gotchyas
We have used GuzzleHttpMock enough internally to feel comfortable using it on production projects, but also enough to know that there are a few "gotchyas". Hopefully, knowing these issues up-front will prevent much conflict between your forehead and your desk.
If you'd like to take a shot at resolving any of these issues, take a look at our contribution guidelines.
Unspecified expectations
In the current version of GuzzleHttpMock, any expectations which are not specified will result in a failed request.
$httpMock ->shouldReceiveRequest() ->withUrl('http://www.example.com/foo'); $guzzleClient->get('/foo', [ 'query' => ['foo' => 'bar'] ]); $httpMock->verify(); // UnexpectedHttpRequestException: Request does not match any expectation: // Request query params does not match any expectation: Actual: [ 'foo' => 'bar' ], Expected: []
You might argue that it would make more sense for the RequestExpectation to accept any value for unspecified expectations by default. And you might be right. Future versions of GuzzleHttpMock may do just that.
Where's my UnexpectedRequestException?
There are a couple of possible culprits here:
-
Make sure you're calling
Mock::verify()
. If you're using a testing framework (eg PHPUnit), you can putverify()
in thetearDown
method. -
Another exception may be thrown before you had a chance to verify your request expectations.
Solving #2 can be a little tricky. If a RequestExpectation cannot be matched, GuzzleHttpClient will not respond with your mock response, which may cause other code to break before you have a chance to call verify()
.
If you're calling verify()
in your test tearDown
, you may want to try adding another verify()
call immediately after the http request is made.
You can also try wrapping the offending code in a try...catch
block, to give the UnexpectedRequestException
priority.
$this->httpMock ->shouldReceiveRequest() ->withXYZ() ->andRespondWith($aValidResponse); try { $subjectUnderTest->doSomethingWhichExpectsAValidHttpResponse(); } catch (\Exception $ex) { // uh oh, $subjectUnderTest made an unexpected request, // and now if does not have a valid response to work with! // Let's check our http mock, and see what happened $httpMock->verify(); // If it's not a request expectation problem, throw the original error $throw ex; }
That's more verbosity than you may want in all of your tests, but it can be helpful if you're debugging.
Why's it doing that thing I don't think it should do?
I don't know. That's really weird. Bummer...
Hey, why don't you open a new issue and tell us about it? Maybe we can help.
Contributing
For that warm fuzzy open-sourcey feeling, contribute to GuzzleHttpMock today!
We only ask that you include PHPUnit tests, and update documentation as needed. Also, if it's not an open issue or on our wish list, you might want to open an issue first, to make sure you're headed in the right direction.
Wish List
Take a look at the "Gotchyas" section for some things that could be fixed. Have another idea? Open an issue, and we'll talk.