Created
May 17, 2019 08:51
-
-
Save macghriogair/c5d330ea79df98385bf95957f9a3de8b to your computer and use it in GitHub Desktop.
[Behat API Context] #guzzle #behat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Tests\Behat; | |
use Behat\Behat\Context\ClosuredContextInterface; | |
use Behat\Behat\Context\TranslatedContextInterface; | |
use Behat\Exception\PendingException; | |
use Behat\Gherkin\Node\PyStringNode; | |
use Behat\Gherkin\Node\TableNode; | |
use Behat\MinkExtension\Context\RawMinkContext; | |
use GuzzleHttp\Client; | |
use GuzzleHttp\Exception\BadResponseException; | |
use GuzzleHttp\Psr7\Request; | |
use Illuminate\Support\Facades\App; | |
use PHPUnit\Framework\Assert as PHPUnit; | |
/** | |
* Features context. | |
*/ | |
abstract class ApiContextBase extends RawMinkContext | |
{ | |
/** | |
* The Guzzle HTTP Client. | |
*/ | |
protected $client; | |
/** | |
* The current resource | |
*/ | |
protected $resource; | |
/** | |
* The request payload | |
*/ | |
protected $requestPayload; | |
/** | |
* The Guzzle HTTP Response. | |
*/ | |
protected $response; | |
/** | |
* The decoded response object. | |
*/ | |
protected $responsePayload; | |
/** | |
* The current scope within the response payload | |
* which conditions are asserted against. | |
*/ | |
protected $scope; | |
protected $headers = []; | |
/** | |
* Initializes context. | |
* Every scenario gets it's own context object. | |
* | |
* @param array $parameters context parameters (set them up through behat.yml) | |
*/ | |
public function __construct(array $parameters) | |
{ | |
require __DIR__ . '/../bootstrap.php'; | |
$config = [ | |
'base_uri' => self::runningInContainer() ? $parameters['docker_url'] : $parameters['default_url'], | |
'allow_redirects' => false | |
]; | |
$this->headers = [ | |
'Accept' => 'application/json', | |
'Content-Type' => 'application/json' | |
// 'Content-Type' => 'application/x-www-form-urlencoded' | |
]; | |
$this->client = new Client($config); | |
} | |
public static function runningInContainer() : bool | |
{ | |
return file_exists('/.dockerenv'); | |
} | |
public function setHeader($key, $value) | |
{ | |
$this->headers[$key] = $value; | |
return $this; | |
} | |
/** | |
* @Given /^I have the payload:$/ | |
*/ | |
public function iHaveThePayload(PyStringNode $jsonString) | |
{ | |
// $payload = json_decode($jsonString->getRaw(), true); | |
$this->requestPayload = $jsonString->getRaw(); | |
} | |
/** | |
* @When /^I request "(GET|PUT|POST|DELETE|PATCH) ([^"]*)"$/ | |
*/ | |
public function iRequest($httpMethod, $resource) | |
{ | |
$options = []; | |
$this->resource = $resource; | |
$method = strtolower($httpMethod); | |
$this->lastRequest = new Request( | |
$method, | |
$this->resource, | |
$this->headers, | |
$this->requestPayload | |
); | |
try { | |
$this->response = $this->client->send($this->lastRequest, $options); | |
} catch (BadResponseException $e) { | |
$response = $e->getResponse(); | |
// Sometimes the request will fail, at which point we have | |
// no response at all. Let Guzzle give an error here, it's | |
// pretty self-explanatory. | |
if ($response === null) { | |
throw $e; | |
} | |
$this->response = $e->getResponse(); | |
} | |
} | |
/** | |
* @Then /^I get a "([^"]*)" response$/ | |
*/ | |
public function iGetAResponse($statusCode) | |
{ | |
$response = $this->getResponse(); | |
$contentType = $response->getHeader('Content-Type'); | |
if (is_array($contentType)) { | |
$contentType = $contentType[0]; | |
} | |
if ($contentType === 'application/json') { | |
$bodyOutput = $response->getBody(); | |
} else { | |
$bodyOutput = 'Output is '.$contentType.', which is not JSON and is therefore scary. Run the request manually.'; | |
} | |
PHPUnit::assertSame((int) $statusCode, (int) $this->getResponse()->getStatusCode(), $bodyOutput); | |
} | |
/** | |
* @Given /^the "([^"]*)" property equals "([^"]*)"$/ | |
*/ | |
public function thePropertyEquals($property, $expectedValue) | |
{ | |
$payload = $this->getScopePayload(); | |
$actualValue = $this->arrayGet($payload, $property); | |
PHPUnit::assertEquals( | |
$actualValue, | |
$expectedValue, | |
"Asserting the [$property] property in current scope equals [$expectedValue]: ".json_encode($payload) | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property exists$/ | |
*/ | |
public function thePropertyExists($property) | |
{ | |
$payload = $this->getScopePayload(); | |
$message = sprintf( | |
'Asserting the [%s] property exists in the scope [%s]: %s', | |
$property, | |
$this->scope, | |
json_encode($payload) | |
); | |
if (is_object($payload)) { | |
PHPUnit::assertTrue(array_key_exists($property, get_object_vars($payload)), $message); | |
} else { | |
PHPUnit::assertTrue(array_key_exists($property, $payload), $message); | |
} | |
} | |
/** | |
* @Given /^the "([^"]*)" property is an array$/ | |
*/ | |
public function thePropertyIsAnArray($property) | |
{ | |
$payload = $this->getScopePayload(); | |
$actualValue = $this->arrayGet($payload, $property); | |
PHPUnit::assertTrue( | |
is_array($actualValue), | |
"Asserting the [$property] property in current scope [{$this->scope}] is an array: ".json_encode($payload) | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is an object$/ | |
*/ | |
public function thePropertyIsAnObject($property) | |
{ | |
$payload = $this->getScopePayload(); | |
$actualValue = $this->arrayGet($payload, $property); | |
PHPUnit::assertTrue( | |
is_object($actualValue), | |
"Asserting the [$property] property in current scope [{$this->scope}] is an object: ".json_encode($payload) | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is an empty array$/ | |
*/ | |
public function thePropertyIsAnEmptyArray($property) | |
{ | |
$payload = $this->getScopePayload(); | |
$scopePayload = $this->arrayGet($payload, $property); | |
PHPUnit::assertTrue( | |
is_array($scopePayload) and $scopePayload === [], | |
"Asserting the [$property] property in current scope [{$this->scope}] is an empty array: ".json_encode($payload) | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property contains (\d+) items$/ | |
*/ | |
public function thePropertyContainsItems($property, $count) | |
{ | |
$payload = $this->getScopePayload(); | |
PHPUnit::assertCount( | |
$count, | |
$this->arrayGet($payload, $property), | |
"Asserting the [$property] property contains [$count] items: ".json_encode($payload) | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is an integer$/ | |
*/ | |
public function thePropertyIsAnInteger($property) | |
{ | |
$payload = $this->getScopePayload(); | |
PHPUnit::isType( | |
'int', | |
$this->arrayGet($payload, $property), | |
"Asserting the [$property] property in current scope [{$this->scope}] is an integer: ".json_encode($payload) | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is a string$/ | |
*/ | |
public function thePropertyIsAString($property) | |
{ | |
$payload = $this->getScopePayload(); | |
PHPUnit::isType( | |
'string', | |
$this->arrayGet($payload, $property), | |
"Asserting the [$property] property in current scope [{$this->scope}] is a string: ".json_encode($payload) | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is a string equalling "([^"]*)"$/ | |
*/ | |
public function thePropertyIsAStringEqualling($property, $expectedValue) | |
{ | |
$payload = $this->getScopePayload(); | |
$this->thePropertyIsAString($property); | |
$actualValue = $this->arrayGet($payload, $property); | |
PHPUnit::assertSame( | |
$actualValue, | |
$expectedValue, | |
"Asserting the [$property] property in current scope [{$this->scope}] is a string equalling [$expectedValue]." | |
); | |
} | |
/** | |
* @Then /^the "([^"]*)" property is a string containing "([^"]*)"$/ | |
*/ | |
public function thePropertiesIsAStringContaining($property, $expectedValue) | |
{ | |
$payload = $this->getScopePayload(); | |
$this->thePropertyIsAString($property); | |
$actualValue = $this->arrayGet($payload, $property); | |
PHPUnit::assertContains( | |
$expectedValue, | |
$actualValue, | |
"Asserting the [$property] property in current scope [{$this->scope}] is a string containing [$expectedValue]." | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is a boolean$/ | |
*/ | |
public function thePropertyIsABoolean($property) | |
{ | |
$payload = $this->getScopePayload(); | |
PHPUnit::assertTrue( | |
gettype($this->arrayGet($payload, $property)) == 'boolean', | |
"Asserting the [$property] property in current scope [{$this->scope}] is a boolean." | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is a boolean equalling "([^"]*)"$/ | |
*/ | |
public function thePropertyIsABooleanEqualling($property, $expectedValue) | |
{ | |
$payload = $this->getScopePayload(); | |
$actualValue = $this->arrayGet($payload, $property); | |
if (! in_array($expectedValue, ['true', 'false'])) { | |
throw new \InvalidArgumentException("Testing for booleans must be represented by [true] or [false]."); | |
} | |
$this->thePropertyIsABoolean($property); | |
PHPUnit::assertSame( | |
$actualValue, | |
$expectedValue == 'true', | |
"Asserting the [$property] property in current scope [{$this->scope}] is a boolean equalling [$expectedValue]." | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is an integer equalling (\d+)$/ | |
*/ | |
public function thePropertyIsAnIntegerEqualling($property, $expectedValue) | |
{ | |
$payload = $this->getScopePayload(); | |
$actualValue = $this->arrayGet($payload, $property); | |
$this->thePropertyIsAnInteger($property); | |
PHPUnit::assertSame( | |
$actualValue, | |
(int) $expectedValue, | |
"Asserting the [$property] property in current scope [{$this->scope}] is an integer equalling [$expectedValue]." | |
); | |
} | |
/** | |
* @Given /^the "([^"]*)" property is either:$/ | |
*/ | |
public function thePropertyIsEither($property, PyStringNode $options) | |
{ | |
$payload = $this->getScopePayload(); | |
$actualValue = $this->arrayGet($payload, $property); | |
$valid = explode("\n", (string) $options); | |
PHPUnit::assertTrue( | |
in_array($actualValue, $valid), | |
sprintf( | |
"Asserting the [%s] property in current scope [{$this->scope}] is in array of valid options [%s].", | |
$property, | |
implode(', ', $valid) | |
) | |
); | |
} | |
/** | |
* @Given /^scope into the first "([^"]*)" property$/ | |
*/ | |
public function scopeIntoTheFirstProperty($scope) | |
{ | |
$this->scope = "{$scope}.0"; | |
} | |
/** | |
* @Given /^scope into the "([^"]*)" property$/ | |
*/ | |
public function scopeIntoTheProperty($scope) | |
{ | |
$this->scope = $scope; | |
} | |
/** | |
* @Given /^the following properties exist:$/ | |
*/ | |
public function thePropertiesExist(PyStringNode $propertiesString) | |
{ | |
foreach (explode("\n", (string) $propertiesString) as $property) { | |
$this->thePropertyExists($property); | |
} | |
} | |
/** | |
* @Given /^reset scope$/ | |
*/ | |
public function resetScope() | |
{ | |
$this->scope = null; | |
} | |
/** | |
* @Transform /^(\d+)$/ | |
*/ | |
public function castStringToNumber($string) | |
{ | |
return intval($string); | |
} | |
/** | |
* @Then print response | |
*/ | |
public function printResponse() | |
{ | |
print_r($this->getResponse()); | |
} | |
/** | |
* @Then print response body | |
*/ | |
public function printResponseBody() | |
{ | |
print_r((string) $this->getResponse()->getBody()); | |
} | |
/** | |
* Checks the response exists and returns it. | |
* | |
* @return Guzzle\Http\Message\Response | |
*/ | |
protected function getResponse() | |
{ | |
if (! $this->response) { | |
throw new \Exception("You must first make a request to check a response."); | |
} | |
return $this->response; | |
} | |
/** | |
* Return the response payload from the current response. | |
* | |
* @return mixed | |
*/ | |
protected function getResponsePayload() | |
{ | |
if (! $this->responsePayload) { | |
$json = json_decode($this->getResponse()->getBody(true)); | |
if (json_last_error() !== JSON_ERROR_NONE) { | |
$message = 'Failed to decode JSON body '; | |
switch (json_last_error()) { | |
case JSON_ERROR_DEPTH: | |
$message .= '(Maximum stack depth exceeded).'; | |
break; | |
case JSON_ERROR_STATE_MISMATCH: | |
$message .= '(Underflow or the modes mismatch).'; | |
break; | |
case JSON_ERROR_CTRL_CHAR: | |
$message .= '(Unexpected control character found).'; | |
break; | |
case JSON_ERROR_SYNTAX: | |
$message .= '(Syntax error, malformed JSON).'; | |
break; | |
case JSON_ERROR_UTF8: | |
$message .= '(Malformed UTF-8 characters, possibly incorrectly encoded).'; | |
break; | |
default: | |
$message .= '(Unknown error).'; | |
break; | |
} | |
throw new \Exception($message); | |
} | |
$this->responsePayload = $json; | |
} | |
return $this->responsePayload; | |
} | |
/** | |
* Returns the payload from the current scope within | |
* the response. | |
* | |
* @return mixed | |
*/ | |
protected function getScopePayload() | |
{ | |
$payload = $this->getResponsePayload(); | |
if (! $this->scope) { | |
return $payload; | |
} | |
return $this->arrayGet($payload, $this->scope); | |
} | |
/** | |
* Get an item from an array using "dot" notation. | |
* | |
* @copyright Taylor Otwell | |
* @link http://laravel.com/docs/helpers | |
* @param array $array | |
* @param string $key | |
* @param mixed $default | |
* @return mixed | |
*/ | |
protected function arrayGet($array, $key) | |
{ | |
if (is_null($key)) { | |
return $array; | |
} | |
foreach (explode('.', $key) as $segment) { | |
if (is_object($array)) { | |
if (! isset($array->{$segment})) { | |
return; | |
} | |
$array = $array->{$segment}; | |
} elseif (is_array($array)) { | |
if (! array_key_exists($segment, $array)) { | |
return; | |
} | |
$array = $array[$segment]; | |
} | |
} | |
return $array; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment