systemhaus/guzzle-http-mock

用于验证使用 Guzzle Http 客户端发送的请求并模拟响应的模拟库。

v2.1.1 2021-06-08 14:24 UTC

README

用于验证使用 Guzzle Http 客户端 发送的请求以及模拟响应的模拟库。

这是原始版本的一个分支。这个版本进行了修改以支持 Guzzle 6,并进行了其他一些小的更改。

安装

Composer

您可以使用 composer 安装 GuzzleHttpMock。

php composer.phar require --dev systemhaus/guzzle-http-mock

概述

GuzzleHttpMock 允许您设置 Http 请求期望和模拟响应。

// Create a mock object
$httpMock = new \Aeris\GuzzleHttp\Mock();

// Create a guzzle http client and attach the mock handler
$guzzleClient = new \GuzzleHttp\Client([
	'base_url' => 'http://www.example.com',
	'handler' => $httpMock->getHandlerStackWithMiddleware();
]);

// 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', ['json' => ['foo' => 'bar'] ]);
json_decode((string) $response->getBody(), true) == ['faz' => 'baz'];  // true
$response->getStatusCode() == 200;      // true
$httpMock->verify();                    // all good.

// Make an unexpected request
$guzzleClient->post('/bar', ['json' => ['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' ]

它是如何工作的?

当 GuzzleHttpMock 处理器附加到 Guzzle Http 客户端时,它将拦截客户端发出的所有请求。每当发起一个请求时,模拟将检查请求是否符合设定的期望,并向匹配的请求发送响应。如果您的客户端使用自定义中间件,则可以将它附加到模拟处理器的处理器堆栈中。

调用 $httpMock->verify() 检查是否已发出所有期望的请求,并对任何意外的请求进行抱怨。

用法

附加到 Guzzle 客户端

要开始拦截 Http 请求,GuzzleHttpMock 必须附加到 GuzzleClient

// Create a mock object
$httpMock = new \Aeris\GuzzleHttp\Mock();

// Create a guzzle http client and attach mock handler
$guzzleClient = new \GuzzleHttp\Client([
	'base_url' => 'http://www.example.com',
	'handler' => $httpMock->getHandlerStackWithMiddleware()
]);

创建请求期望

shouldReceiveRequest 方法返回一个 \Aeris\GuzzleHttpMock\Expectation\RequestExpectation 对象。

$requestExpectation = $httpMock->shouldReceiveRequest();

RequestExpectation 对象使用 withXyz 方法来设置期望

$requestExpectation->withUrl('http://www.example.com/foo');

期望设置器是可链接的,允许流畅的接口

$httpMock
    ->shouldReceiveRequest()
    ->withUrl('http://www.example.com/foo')
    ->withMethod('POST');

可用期望

以下期望在 \Aeris\GuzzleHttpMock\Expectation\RequestExpectation 对象上可用。

默认期望

默认情况下,期望请求只被发送一次,使用 Http 方法 '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');

直接设置期望请求

除了单独指定请求期望外,您还可以直接设置一个 Psr\Http\Message\RequestInterface 对象作为期望。

$expectedRequest = new Request(
    'PUT',
    'http://www.example.com/foo?faz=baz',
    [
		'body'    => json_encode(['shazaam' => 'kablooey']),
		'headers' => [
			'Content-Type' => 'application/json'
		],
	]
);

$httpClient->shouldReceiveRequest($expectedRequest);

自定义期望

所有期望方法都接受一个值或一个 callable 作为参数。通过传递一个可调用对象,您可以创建自定义期望。例如

$httpMock
    ->shouldReceiveRequest()
    ->withBodyParams(function($actualParams) {
        return $actualParams['foo'] === 'bar';
    });

在这种情况下,如果实际请求体有一个不等于 barfoo 参数,期望将失败。

GuzzleHttpMock 还提供了一些内置的自定义期望。例如

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());

模拟响应

当请求与期望匹配时,GuzzleHttpMock 将拦截请求,并使用模拟响应进行响应。

$httpMock
  ->shouldReceiveRequest()
  ->withMethod('GET')
  ->withUrl('http://www.example.com/foo')
  ->andRespondWithJson(['foo' => 'bar']);

$response = $guzzleClient->get('/foo');
$response->json() == ['foo' => 'bar'];  // true

可用响应

以下方法可用于模拟响应

直接设置模拟响应

您可以使用响应对象直接模拟响应

$response = new \GuzzleHttp\Psr7\Response(
    200,
    ['Content-Type' = 'application/json'],
    json_encode(['foo' => 'bar' ]
);

$httpMock
    ->shouldReceiveRequest()
    ->withMethod('GET')
    ->withUrl('http://www.example.com/foo')
    ->andResponseWith($response);

验证期望

可以使用 \Aeris\GuzzleHttpMock::verify() 方法验证期望

$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'.

与 PHPUnit 一起使用

当使用 GuzzleHttpMock 与 PHPUnit 结合时,确保在测试的清理阶段添加 Mock::verify()

class MyUnitTest extends \PHPUnit_Framework_TestCase {
    private $guzzleClient;
    private $httpMock;

    public function setUp() {
    	// Setup your guzzle client and mock
    	$this->httpMock = new \Aeris\GuzzleHttpMock();
                
    	$this->guzzleClient = new \GuzzleHttp\Client([
			'base_url' => 'http://www.example.com',
			'handler' => $this->httpMock->getHandlerStackWithMiddleware();
		]);
   	}

    public function tearDown() {
    	// Make sure all request expectations are met.
    	$this->httpMock->verify();
        // Failed expectations will throw an \Aeris\GuzzleHttpMock\Exception\UnexpectedHttpRequestException
    }
}

注意事项

我们在内部已经足够熟悉 GuzzleHttpMock,可以放心地在生产项目中使用它,但也足够了解其中存在一些“陷阱”。希望了解这些问题,可以避免你头疼和电脑桌之间发生冲突。

如果你想尝试解决这些问题中的任何一个,请查看我们的贡献指南

未指定的期望

在当前版本的 GuzzleHttpMock 中,任何未指定的预期都会导致请求失败。

$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: []

你可能会认为,对于 RequestExpectation 来说,默认接受任何未指定的值更有意义。你可能是对的。GuzzleHttpMock 的未来版本可能会这样做。

我的 UnexpectedRequestException 去哪儿了?

这里有几个可能的“罪魁祸首”。

  1. 确保你调用了 Mock::verify()。如果你使用的是测试框架(例如 PHPUnit),你可以在 tearDown 方法中添加 verify()

  2. 在有机会验证你的请求预期之前,可能已经抛出了另一个异常。

解决第2个问题可能有点棘手。如果 RequestExpectation 无法匹配,GuzzleHttpClient 不会返回你的模拟响应,这可能会导致其他代码在你有机会调用 verify() 之前就崩溃。

如果你在测试的 tearDown 中调用 verify(),你可能想在 HTTP 请求之后立即添加另一个 verify() 调用。

你也可以尝试将问题代码包裹在 try...catch 块中,以便给 UnexpectedRequestException 以优先权。

$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;
}

这可能比你在所有测试中都想要的更多,但在调试时可能会有所帮助。

为什么它会做我不认为它应该做的事情?

我不知道。这真的很奇怪。真遗憾...

嘿,为什么不打开一个新的问题并告诉我们呢?也许我们可以帮忙。

贡献

为了那种温暖的、开源的感觉,今天为 GuzzleHttpMock 贡献吧!

我们只要求你包括 PHPUnit 测试,并在需要时更新文档。另外,如果不是公开问题或在我们的愿望清单上,你可能首先想打开一个问题,以确保你走的是正确的方向。

愿望清单

请查看“陷阱”部分,了解一些可以修复的问题。还有其他想法?打开一个问题,我们再讨论。