mirror of
https://github.com/Neur0toxine/pock.git
synced 2025-01-30 20:41:44 +03:00
at(N)
, always()
methods, separate PSR-18 exceptions, throw methods for them, roadmap to stable
This commit is contained in:
parent
03fc4e4c2a
commit
21021e5d70
@ -112,12 +112,11 @@ In order to use unsupported serializer you should create a decorator which imple
|
||||
|
||||
# Roadmap to stable
|
||||
|
||||
- [ ] `at(N)` - execute mock only at Nth call.
|
||||
- [ ] `after(N)` - allow mock execution only after Nth call (for using with repeat or always).
|
||||
- [ ] `always()` - always execute this mock (removes mock expiration).
|
||||
- [x] `at(N)` - execute mock only at Nth call.
|
||||
- [x] `always()` - always execute this mock (removes mock expiration).
|
||||
- [ ] Regexp matchers for body, query and path.
|
||||
- [ ] Separate `UniversalMockException` into several exceptions (`PockClientException`, `PockNetworkException`, etc).
|
||||
- [ ] Add methods for easier throwing of exceptions listed in previous entry.
|
||||
- [x] Separate `UniversalMockException` into several exceptions (`PockClientException`, `PockNetworkException`, etc).
|
||||
- [x] Add methods for easier throwing of exceptions listed in previous entry.
|
||||
- [ ] `replyCallback` - reply using specified callback.
|
||||
- [ ] `replyFactory` - reply using specified response factory (provide corresponding interface).
|
||||
- [ ] Compare XML bodies using `DOMDocument`, fallback to text comparison in case of problems.
|
||||
|
10
phpmd.xml
10
phpmd.xml
@ -10,7 +10,15 @@
|
||||
<rule ref="rulesets/design.xml" />
|
||||
<rule ref="rulesets/cleancode.xml" />
|
||||
<rule ref="rulesets/codesize.xml" />
|
||||
<rule ref="rulesets/naming.xml" />
|
||||
<rule ref="rulesets/naming.xml">
|
||||
<exclude name="ShortMethodName" />
|
||||
</rule>
|
||||
|
||||
<rule ref="rulesets/naming.xml/ShortMethodName">
|
||||
<properties>
|
||||
<property name="minimum" value="2" />
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<exclude-pattern>tests/*</exclude-pattern>
|
||||
</ruleset>
|
||||
|
@ -74,17 +74,19 @@ class Client implements ClientInterface, HttpClient, HttpAsyncClient
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($mock->getMatcher()->matches($request)) {
|
||||
if ($mock->matches($request)) {
|
||||
if (null !== $mock->getResponse()) {
|
||||
$mock->registerHit();
|
||||
|
||||
return new HttpFulfilledPromise($mock->getResponse());
|
||||
}
|
||||
|
||||
if (null !== $mock->getThrowable()) {
|
||||
$throwable = $mock->getThrowable($request);
|
||||
|
||||
if (null !== $throwable) {
|
||||
$mock->registerHit();
|
||||
|
||||
return new HttpRejectedPromise($mock->getThrowable());
|
||||
return new HttpRejectedPromise($throwable);
|
||||
}
|
||||
|
||||
throw new IncompleteMockException($mock);
|
||||
|
45
src/Exception/AbstractRequestAwareException.php
Normal file
45
src/Exception/AbstractRequestAwareException.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP 7.3
|
||||
*
|
||||
* @category AbstractRequestAwareException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
|
||||
namespace Pock\Exception;
|
||||
|
||||
use Exception;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractRequestAwareException
|
||||
*
|
||||
* @category AbstractRequestAwareException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
class AbstractRequestAwareException extends Exception
|
||||
{
|
||||
/** @var RequestInterface */
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRequest(RequestInterface $request): self
|
||||
{
|
||||
$instance = new static($this->message, $this->code, $this->getPrevious()); // @phpstan-ignore-line
|
||||
$instance->request = $request;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Psr\Http\Message\RequestInterface
|
||||
*/
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
}
|
23
src/Exception/PockClientException.php
Normal file
23
src/Exception/PockClientException.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP 7.3
|
||||
*
|
||||
* @category PockClientException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
|
||||
namespace Pock\Exception;
|
||||
|
||||
use Exception;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class PockClientException
|
||||
*
|
||||
* @category PockClientException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
class PockClientException extends Exception implements ClientExceptionInterface
|
||||
{
|
||||
}
|
22
src/Exception/PockNetworkException.php
Normal file
22
src/Exception/PockNetworkException.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP 7.3
|
||||
*
|
||||
* @category PockNetworkException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
|
||||
namespace Pock\Exception;
|
||||
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class PockNetworkException
|
||||
*
|
||||
* @category PockNetworkException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
class PockNetworkException extends AbstractRequestAwareException implements NetworkExceptionInterface
|
||||
{
|
||||
}
|
22
src/Exception/PockRequestException.php
Normal file
22
src/Exception/PockRequestException.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP 7.3
|
||||
*
|
||||
* @category PockRequestException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
|
||||
namespace Pock\Exception;
|
||||
|
||||
use Psr\Http\Client\RequestExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class PockRequestException
|
||||
*
|
||||
* @category PockRequestException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
class PockRequestException extends AbstractRequestAwareException implements RequestExceptionInterface
|
||||
{
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP 7.2
|
||||
*
|
||||
* @category UniversalMockException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
|
||||
namespace Pock\Exception;
|
||||
|
||||
use Exception;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Psr\Http\Client\RequestExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Class UniversalMockException
|
||||
*
|
||||
* @category UniversalMockException
|
||||
* @package Pock\Exception
|
||||
*/
|
||||
class UniversalMockException extends Exception implements
|
||||
ClientExceptionInterface,
|
||||
NetworkExceptionInterface,
|
||||
RequestExceptionInterface
|
||||
{
|
||||
/** @var mixed */
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* UniversalMockException constructor.
|
||||
*
|
||||
* @param mixed $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct('Default mock exception');
|
||||
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
}
|
46
src/Mock.php
46
src/Mock.php
@ -9,7 +9,12 @@
|
||||
|
||||
namespace Pock;
|
||||
|
||||
use Pock\Exception\PockNetworkException;
|
||||
use Pock\Exception\PockRequestException;
|
||||
use Pock\Matchers\RequestMatcherInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Psr\Http\Client\RequestExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
@ -36,6 +41,9 @@ class Mock implements MockInterface
|
||||
/** @var int */
|
||||
private $maxHits;
|
||||
|
||||
/** @var int */
|
||||
private $matchAt;
|
||||
|
||||
/**
|
||||
* Mock constructor.
|
||||
*
|
||||
@ -43,18 +51,25 @@ class Mock implements MockInterface
|
||||
* @param \Psr\Http\Message\ResponseInterface|null $response
|
||||
* @param \Throwable|null $throwable
|
||||
* @param int $maxHits
|
||||
* @param int $matchAt
|
||||
*/
|
||||
public function __construct(
|
||||
RequestMatcherInterface $matcher,
|
||||
?ResponseInterface $response,
|
||||
?Throwable $throwable,
|
||||
int $maxHits
|
||||
int $maxHits,
|
||||
int $matchAt
|
||||
) {
|
||||
$this->matcher = $matcher;
|
||||
$this->response = $response;
|
||||
$this->throwable = $throwable;
|
||||
$this->matchAt = $matchAt;
|
||||
$this->maxHits = $maxHits;
|
||||
$this->hits = 0;
|
||||
|
||||
if ($this->maxHits < ($matchAt + 1) && -1 !== $this->maxHits) {
|
||||
$this->maxHits = $matchAt + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,7 +77,10 @@ class Mock implements MockInterface
|
||||
*/
|
||||
public function registerHit(): MockInterface
|
||||
{
|
||||
++$this->hits;
|
||||
if (-1 !== $this->maxHits) {
|
||||
++$this->hits;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -71,15 +89,27 @@ class Mock implements MockInterface
|
||||
*/
|
||||
public function available(): bool
|
||||
{
|
||||
return $this->hits < $this->maxHits;
|
||||
return -1 === $this->maxHits || $this->hits < $this->maxHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getMatcher(): RequestMatcherInterface
|
||||
public function matches(RequestInterface $request): bool
|
||||
{
|
||||
return $this->matcher;
|
||||
if ($this->matcher->matches($request)) {
|
||||
if ($this->matchAt <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->matchAt === $this->hits) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->registerHit();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,8 +131,12 @@ class Mock implements MockInterface
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getThrowable(): ?Throwable
|
||||
public function getThrowable(RequestInterface $request): ?Throwable
|
||||
{
|
||||
if ($this->throwable instanceof PockRequestException || $this->throwable instanceof PockNetworkException) {
|
||||
return $this->throwable->setRequest($request);
|
||||
}
|
||||
|
||||
return $this->throwable;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
namespace Pock;
|
||||
|
||||
use Pock\Matchers\RequestMatcherInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
@ -36,11 +37,14 @@ interface MockInterface
|
||||
public function available(): bool;
|
||||
|
||||
/**
|
||||
* Returns matcher for the request.
|
||||
* Returns true if underlying matcher has matched provided request.
|
||||
* It also returns false if matcher has matched request but hits condition is not met yet.
|
||||
*
|
||||
* @return \Pock\Matchers\RequestMatcherInterface
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getMatcher(): RequestMatcherInterface;
|
||||
public function matches(RequestInterface $request): bool;
|
||||
|
||||
/**
|
||||
* Returns response which should be used as mock data.
|
||||
@ -52,7 +56,9 @@ interface MockInterface
|
||||
/**
|
||||
* Returns the throwable which will be thrown as mock data.
|
||||
*
|
||||
* @param \Psr\Http\Message\RequestInterface $request This request may be set into exception if possible
|
||||
*
|
||||
* @return \Throwable|null
|
||||
*/
|
||||
public function getThrowable(): ?Throwable;
|
||||
public function getThrowable(RequestInterface $request): ?Throwable;
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ namespace Pock;
|
||||
use Diff\ArrayComparer\StrictArrayComparer;
|
||||
use Pock\Enum\RequestMethod;
|
||||
use Pock\Enum\RequestScheme;
|
||||
use Pock\Exception\PockClientException;
|
||||
use Pock\Exception\PockNetworkException;
|
||||
use Pock\Exception\PockRequestException;
|
||||
use Pock\Matchers\AnyRequestMatcher;
|
||||
use Pock\Matchers\BodyMatcher;
|
||||
use Pock\Matchers\CallbackRequestMatcher;
|
||||
@ -45,6 +48,7 @@ use Throwable;
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
|
||||
* @SuppressWarnings(PHPMD.TooManyMethods)
|
||||
*/
|
||||
class PockBuilder
|
||||
{
|
||||
@ -64,6 +68,9 @@ class PockBuilder
|
||||
/** @var int */
|
||||
private $maxHits;
|
||||
|
||||
/** @var int */
|
||||
private $matchAt;
|
||||
|
||||
/** @var \Pock\MockInterface[] */
|
||||
private $mocks;
|
||||
|
||||
@ -329,6 +336,8 @@ class PockBuilder
|
||||
*/
|
||||
public function repeat(int $hits): self
|
||||
{
|
||||
$this->closePrevious();
|
||||
|
||||
if ($hits > 0) {
|
||||
$this->maxHits = $hits;
|
||||
}
|
||||
@ -336,6 +345,49 @@ class PockBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always execute this mock if matched. Mock with this call will not be expired ever.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function always(): self
|
||||
{
|
||||
$this->closePrevious();
|
||||
$this->maxHits = -1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match request only at Nth hit. Previous matches will not be executed.
|
||||
*
|
||||
* **Note:** There IS a catch if you use this with the equal mocks. The test Client will not register hit
|
||||
* for the second mock and the second mock will be executed at N+1 time.
|
||||
*
|
||||
* For example, if you try to send 5 requests with this mocks and log response codes:
|
||||
* ```php
|
||||
* $builder = new PockBuilder();
|
||||
*
|
||||
* $builder->matchHost('example.com')->at(2)->reply(200);
|
||||
* $builder->matchHost('example.com')->at(4)->reply(201);
|
||||
* $builder->always()->reply(400);
|
||||
* ```
|
||||
*
|
||||
* You will get this: 400, 400, 200, 400, 400, 201
|
||||
* Instead of this: 400, 400, 200, 400, 201, 400
|
||||
*
|
||||
* @param int $hit
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function at(int $hit): self
|
||||
{
|
||||
$this->closePrevious();
|
||||
$this->matchAt = $hit - 1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception when request is being sent.
|
||||
*
|
||||
@ -350,6 +402,42 @@ class PockBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an ClientExceptionInterface instance with specified message
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function throwClientException(string $message = 'Pock ClientException'): self
|
||||
{
|
||||
return $this->throwException(new PockClientException($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an NetworkExceptionInterface instance with specified message
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function throwNetworkException(string $message = 'Pock NetworkException'): self
|
||||
{
|
||||
return $this->throwException(new PockNetworkException($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an RequestExceptionInterface instance with specified message
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function throwRequestException(string $message = 'Pock RequestException'): self
|
||||
{
|
||||
return $this->throwException(new PockRequestException($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $statusCode
|
||||
*
|
||||
@ -377,6 +465,7 @@ class PockBuilder
|
||||
$this->responseBuilder = null;
|
||||
$this->throwable = null;
|
||||
$this->maxHits = 1;
|
||||
$this->matchAt = -1;
|
||||
$this->mocks = [];
|
||||
|
||||
return $this;
|
||||
@ -421,12 +510,14 @@ class PockBuilder
|
||||
$this->matcher,
|
||||
$response,
|
||||
$this->throwable,
|
||||
$this->maxHits
|
||||
$this->maxHits,
|
||||
$this->matchAt
|
||||
);
|
||||
$this->matcher = new MultipleMatcher();
|
||||
$this->responseBuilder = null;
|
||||
$this->throwable = null;
|
||||
$this->maxHits = 1;
|
||||
$this->matchAt = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,14 @@ namespace Pock\Tests;
|
||||
|
||||
use Pock\Enum\RequestMethod;
|
||||
use Pock\Enum\RequestScheme;
|
||||
use Pock\Exception\UniversalMockException;
|
||||
use Pock\Exception\UnsupportedRequestException;
|
||||
use Pock\PockBuilder;
|
||||
use Pock\TestUtils\PockTestCase;
|
||||
use Pock\TestUtils\SimpleObject;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Psr\Http\Client\RequestExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class PockBuilderTest
|
||||
@ -35,7 +35,7 @@ class PockBuilderTest extends PockTestCase
|
||||
->createRequest(RequestMethod::GET, self::TEST_URI));
|
||||
}
|
||||
|
||||
public function testThrowException(): void
|
||||
public function testThrowClientException(): void
|
||||
{
|
||||
$this->expectException(ClientExceptionInterface::class);
|
||||
|
||||
@ -43,7 +43,37 @@ class PockBuilderTest extends PockTestCase
|
||||
$builder->matchMethod(RequestMethod::GET)
|
||||
->matchScheme(RequestScheme::HTTPS)
|
||||
->matchHost(self::TEST_HOST)
|
||||
->throwException(new UniversalMockException('Boom!'));
|
||||
->throwClientException();
|
||||
|
||||
$builder->getClient()->sendRequest(
|
||||
self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI)
|
||||
);
|
||||
}
|
||||
|
||||
public function testThrowNetworkException(): void
|
||||
{
|
||||
$this->expectException(NetworkExceptionInterface::class);
|
||||
|
||||
$builder = new PockBuilder();
|
||||
$builder->matchMethod(RequestMethod::GET)
|
||||
->matchScheme(RequestScheme::HTTPS)
|
||||
->matchHost(self::TEST_HOST)
|
||||
->throwNetworkException();
|
||||
|
||||
$builder->getClient()->sendRequest(
|
||||
self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI)
|
||||
);
|
||||
}
|
||||
|
||||
public function testThrowRequestException(): void
|
||||
{
|
||||
$this->expectException(RequestExceptionInterface::class);
|
||||
|
||||
$builder = new PockBuilder();
|
||||
$builder->matchMethod(RequestMethod::GET)
|
||||
->matchScheme(RequestScheme::HTTPS)
|
||||
->matchHost(self::TEST_HOST)
|
||||
->throwRequestException();
|
||||
|
||||
$builder->getClient()->sendRequest(
|
||||
self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI)
|
||||
@ -511,4 +541,50 @@ EOF;
|
||||
);
|
||||
self::assertEquals('Second token (post)', $response->getBody()->getContents());
|
||||
}
|
||||
|
||||
public function testAlways(): void
|
||||
{
|
||||
$builder = new PockBuilder();
|
||||
$builder->matchMethod(RequestMethod::GET)
|
||||
->matchUri(self::TEST_URI)
|
||||
->always()
|
||||
->reply(200)
|
||||
->withHeader('Content-Type', 'text/plain')
|
||||
->withBody('Successful');
|
||||
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$response = $builder->getClient()->sendRequest(
|
||||
self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI)
|
||||
);
|
||||
|
||||
self::assertEquals(200, $response->getStatusCode());
|
||||
self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders());
|
||||
self::assertEquals('Successful', $response->getBody()->getContents());
|
||||
}
|
||||
}
|
||||
|
||||
public function testAt(): void
|
||||
{
|
||||
$builder = new PockBuilder();
|
||||
$builder->matchMethod(RequestMethod::GET)
|
||||
->matchUri(self::TEST_URI)
|
||||
->at(2)
|
||||
->reply(200);
|
||||
|
||||
$builder->matchMethod(RequestMethod::GET)
|
||||
->matchUri(self::TEST_URI)
|
||||
->at(4)
|
||||
->reply(201);
|
||||
|
||||
$builder->always()->reply(400);
|
||||
$builder->getClient();
|
||||
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$response = $builder->getClient()->sendRequest(
|
||||
self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI)
|
||||
);
|
||||
|
||||
self::assertEquals(1 === $i ? 200 : (4 === $i ? 201 : 400), $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user