Skip to content

Instantly share code, notes, and snippets.

@sveneisenschmidt
Last active December 11, 2015 01:38
Show Gist options
  • Save sveneisenschmidt/4524570 to your computer and use it in GitHub Desktop.
Save sveneisenschmidt/4524570 to your computer and use it in GitHub Desktop.
Test API
<?php
namespace Wildly\Bundle\ApiBundle\Features\Context;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser;
use Symfony\Component\Process\Process;
use Behat\Symfony2Extension\Context\KernelAwareInterface;
use Behat\MinkExtension\Context\MinkContext;
use Behat\Behat\Context\BehatContext,
Behat\Behat\Event\SuiteEvent,
Behat\Behat\Event\ScenarioEvent,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
/**
* Feature context.
*/
class FeatureContext extends BehatContext
{
/**
* @BeforeSuite
*/
public static function prepareDatabase(SuiteEvent $event)
{
$drop = self::spawn('app/console doctrine:schema:drop --force --env=test');
$drop->run();
if (!$drop->isSuccessful()) {
throw new \RuntimeException($process->getErrorOutput());
}
$create = self::spawn('app/console doctrine:schema:update --force --env=test');
$create->run();
if (!$create->isSuccessful()) {
throw new \RuntimeException($process->getErrorOutput());
}
$load = self::spawn('app/console doctrine:fixtures:load --env=test');
$load->run();
if (!$load->isSuccessful()) {
throw new \RuntimeException($process->getErrorOutput());
}
}
/**
* @BeforeSuite
*/
public static function spawn($command, $timout = 3600)
{
$process = new Process($command);
$process->setTimeout(3600);
return $process;
}
/**
*
* @var Guzzle\Service\Client
*/
protected $client;
/**
*
* @var array
*/
protected $parameters = [];
/**
*
* @var array
*/
protected $query = [];
/**
*
* @var array
*/
protected $data = [];
/**
*
* @param array $parameters
*/
public function __construct(array $parameters = [])
{
$this->parameters = new ParameterBag($parameters);
$this->mimetypes = new MimeTypeExtensionGuesser();
}
/**
* @BeforeScenario
*/
public function setup()
{
$this->client = new \Goutte\Client;
$this->query = new ParameterBag();
$this->data = new ParameterBag();
}
/**
* @Given /^that I want to "([^"]*)" a resource$/
*/
public function thatIWantToAResource($method)
{
if(!in_array($method, ['GET', 'POST', 'PUT','PATCH', 'DELETE', 'HEAD'])) {
throw new \Exception(sprintf('Unknown method %s', $method));
}
$this->method = strtoupper($method);
}
/**
* @Given /^that its query "([^"]*)" is "([^"]*)"$/
*/
public function thatItsQueryIs($key, $value)
{
$this->query->set($key, $value);
}
/**
* @Given /^that its data "([^"]*)" is "([^"]*)"$/
*/
public function thatItsDataIs($key, $value)
{
$this->data->set($key, $value);
}
/**
* @When /^I request "([^"]*)"$/
*/
public function iRequest($path)
{
if(false === $this->parameters->has('base_url')) {
throw new \Exception(sprintf('Missing parameter %s', 'base_url'));
}
$base = $this->parameters->get('base_url');
$data = $this->data->all();
$query = http_build_query($this->query->all());
$url = sprintf('%s/%s%s%s',
rtrim($base, '/'),
ltrim($path, '/'),
(true === empty($query)) ? '' :
((false !== strpos($path, '?')) ? '&' : '?'),
$query
);
$this->client->request($this->method, $url, $data);
}
/**
* @Given /^the response has a "([^"]*)" property$/
*/
public function theResponseHasAProperty($key)
{
$response = $this->client->getResponse();
$content = $response->getContent();
$headers = $response->getHeaders();
if(true === empty($headers['Content-Type'])) {
throw new \Exception('Header [Content-Type] not returned');
}
$mimetype = $headers['Content-Type'][0];
$data = $this->decode($mimetype, $content);
$bag = new ParameterBag($data);
if(null === $bag->get($key, null, true)) {
throw new \Exception(sprintf('Unknown property %s', $key));
}
}
/**
* @Given /^the response should have the properties$/
*/
public function theResponseShouldHaveTheProperties(PyStringNode $string)
{
foreach($string->getLines() as $line) {
$this->theResponseHasAProperty($line);
}
}
/**
* @Given /^the response header "([^"]*)" is set$/
*/
public function theResponseHeaderIsSet($key)
{
$response = $this->client->getResponse();
$content = $response->getContent();
$headers = $response->getHeaders();
if(false === array_key_exists($key, $headers) || true === empty($headers[$key])) {
throw new \Exception(sprintf('Header [%s] does not exist or is empty', $key));
}
}
// ATTENTION
/**
* @Then /^the response status code should be (\d+)$/
*/
public function theResponseStatusCodeShouldBe($code)
{
$response = $this->client->getResponse();
if((int)$code !== ($status = $response->getStatus())) {
throw new \Exception(sprintf('Wrong status code returned (%s)', $status));
}
}
/**
* @Then /^the response format is "([^"]*)"$/
*/
public function theResponseFormatIs($format)
{
$this->theResponseHeaderIsSet('Content-Type');
$format = strtolower($format);
$response = $this->client->getResponse();
$content = $response->getContent();
$headers = $response->getHeaders();
$mimetype = $headers['Content-Type'][0];
if($format !== $mimetype && $format !== $this->mimetypes->guess($mimetype)) {
throw new \Exception(sprintf('Wrong Content-Type %s returned', $mimetype));
}
try {
$this->decode($mimetype, $content);
} catch(Exception $e) {
throw new \Exception('Response is no valid JSON');
}
}
/**
* @Given /^the type of the "([^"]*)" property is "([^"]*)"$/
*/
public function theTypeOfThePropertyIs($key, $type)
{
$this->theResponseHeaderIsSet('Content-Type');
$this->theResponseHasAProperty($key);
$response = $this->client->getResponse();
$content = $response->getContent();
$headers = $response->getHeaders();
$mimetype = $headers['Content-Type'][0];
$data = $this->decode($mimetype, $content);
$bag = new ParameterBag($data);
$value = $bag->get($key, null, true);
switch($type) {
case 'integer':
if(false === is_int($value)) {
throw new \Exception(sprintf('Property %s is not of type %s', $key, $type));
}
break;
case 'numeric':
if(false === is_numeric($value)) {
throw new \Exception(sprintf('Property %s is not of type %s', $key, $type));
}
break;
case 'string':
if(false === is_string($value)) {
throw new \Exception(sprintf('Property %s is not of type %s', $key, $type));
}
break;
default:
throw new \Exception(sprintf('Unsupported type %s', $type));
}
}
/**
* @Given /^the value of the "([^"]*)" property is (\d+)$/
*/
public function theValueOfThePropertyIs($key, $value)
{
$this->theResponseHeaderIsSet('Content-Type');
$this->theResponseHasAProperty($key);
$response = $this->client->getResponse();
$content = $response->getContent();
$headers = $response->getHeaders();
$mimetype = $headers['Content-Type'][0];
$data = $this->decode($mimetype, $content);
$bag = new ParameterBag($data);
if($value != ($actual = $bag->get($key, null, true))) {
throw new \Exception(sprintf('Property %s has value %s not %s', $key, $value, $actual));
}
}
/**
* @Given /^the value of the "([^"]*)" property is "([^"]*)"$/
*/
public function theValueOfThePropertyIs2($key, $value)
{
$this->theValueOfThePropertyIs($key, $value);
}
/**
* Tries to code simple text based formats
*
* @param string $mimetype
* @param string $content
* @return array
*/
protected function decode($mimetype, $content)
{
switch($mimetype) {
case 'application/json':
if(null === ($data = json_decode($content, true))) {
throw new \Exception('Response is no valid JSON');
}
break;
default:
throw new \Exception('Response body is in unknown format');
}
return $data;
}
/**
* @Given /^the response should be empty$/
*/
public function theResponseShouldBeEmpty()
{
$response = $this->client->getResponse();
$content = $response->getContent();
if(false === empty($content)) {
throw new \Exception('Response body is not empty');
}
}
/**
* @Given /^the response header "([^"]*)" should be set$/
*/
public function theResponseHeaderShouldBeSet($key)
{
$response = $this->client->getResponse();
$content = $response->getContent();
$headers = $response->getHeaders();
if(false === array_key_exists($key, $headers) || true === empty($headers[$key])) {
throw new \Exception(sprintf('Header [%s] does not exist or is empty', $key));
}
}
/**
* @Given /^debug response$/
*/
public function debugResponse()
{
print PHP_EOL;
print_r($this->client->getResponse());
print PHP_EOL;
}
}
# features/pet.feature
Feature: Testing the Pet API
# AUTH
Scenario: Authetication fails: Missing token
Given that I want to "GET" a resource
When I request "/pets/1"
And the response status code should be 400
And the value of the "status" property is "error"
And the value of the "status_text" property is "Bad Request"
And the value of the "message" property is "Missing authentication token."
Scenario: Authetication fails: Wrong token
Given that I want to "GET" a resource
And that its query "_token" is "WRONG_TOKEN"
When I request "/pets/1"
And the response status code should be 400
And the value of the "status" property is "error"
And the value of the "status_text" property is "Bad Request"
And the value of the "message" property is "Invalid authentication token."
# GET
Scenario: Pet exists
Given that I want to "GET" a resource
And that its query "_token" is "TEST_TOKEN"
When I request "/pets/1"
Then the response format is "JSON"
And the response status code should be 200
And the response header "Content-Type" is set
And the response should have the properties
"""
id
created_at
updated_at
creature[id]
title
owner
"""
And the type of the "id" property is "integer"
And the value of the "id" property is 1
Scenario: Pet does not exist
Given that I want to "GET" a resource
And that its query "_token" is "TEST_TOKEN"
When I request "/pets/999999999"
Then the response format is "JSON"
And the response status code should be 404
And the value of the "status" property is "error"
And the value of the "status_text" property is "Not Found"
# PATCH
Scenario: Partially updates Pet
Given that I want to "POST" a resource
And that its query "_token" is "TEST_TOKEN"
And that its query "_method" is "PATCH"
And that its data "title" is "TEST Scenario #PATCH"
When I request "/pets/1"
And the response status code should be 204
And the response should be empty
Scenario: Load updated Pet
Given that I want to "GET" a resource
And that its query "_token" is "TEST_TOKEN"
When I request "/pets/1"
And the response status code should be 200
And the value of the "title" property is "TEST Scenario #PATCH"
# POST
Scenario: Fully update Pet with new data
Given that I want to "POST" a resource
And that its query "_token" is "TEST_TOKEN"
And that its data "title" is "TEST Scenario #POST"
And that its data "description" is "TEST Scenario #POST"
When I request "/pets/1"
And the response status code should be 204
And the response should be empty
Scenario: Load updated Pet
Given that I want to "GET" a resource
And that its query "_token" is "TEST_TOKEN"
When I request "/pets/1"
And the response status code should be 200
And the value of the "title" property is "TEST Scenario #POST"
And the value of the "description" property is "TEST Scenario #POST"
Scenario: Fully update Pet with insufficient data
Given that I want to "POST" a resource
And that its query "_token" is "TEST_TOKEN"
And that its data "title" is "TEST Scenario"
When I request "/pets/1"
And the response status code should be 400
And the value of the "status" property is "error"
And the value of the "status_text" property is "Bad Request"
And the value of the "message" property is "Validation failed"
# PUT
Scenario: Create new Pet
Given that I want to "POST" a resource
And that its query "_method" is "PUT"
And that its query "_token" is "TEST_TOKEN"
And that its data "title" is "TEST Scenario #PUT1"
And that its data "description" is "TEST Scenario #PUT1"
And that its data "owner" is "1"
And that its data "creature" is "1"
When I request "/pets"
And the response status code should be 200
And the value of the "id" property is 11
And the value of the "title" property is "TEST Scenario #PUT1"
And the value of the "description" property is "TEST Scenario #PUT1"
And the value of the "creature[id]" property is 1
And the value of the "owner[id]" property is 1
Scenario: Create new Pet without redirect
Given that I want to "POST" a resource
And that its query "_method" is "PUT"
And that its query "_token" is "TEST_TOKEN"
And that its data "title" is "TEST Scenario #PUT2"
And that its data "description" is "TEST Scenario #PUT2"
And that its data "owner" is "1"
And that its data "creature" is "1"
And do not follow redirects
When I request "/pets"
And the response status code should be 201
And the response should be empty
Scenario: Load creatd Pet manually
Given that I want to "GET" a resource
And that its query "_token" is "TEST_TOKEN"
When I request "/pets/12"
And the response status code should be 200
And the value of the "id" property is 12
And the value of the "title" property is "TEST Scenario #PUT2"
And the value of the "description" property is "TEST Scenario #PUT2"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment