Last active
September 23, 2019 15:22
Example Symfony ApiTestCase (from WIP KnpUniversity Symfony REST tutorial)
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 AppBundle\Tests\Controller\Api; | |
use Doctrine\Common\DataFixtures\Purger\ORMPurger; | |
use Doctrine\ORM\EntityManager; | |
use Exception; | |
use GuzzleHttp\Client; | |
use GuzzleHttp\Message\ResponseInterface; | |
use GuzzleHttp\Subscriber\History; | |
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |
use Symfony\Component\Console\Output\ConsoleOutput; | |
use Symfony\Component\DomCrawler\Crawler; | |
use Symfony\Component\PropertyAccess\Exception\RuntimeException; | |
use Symfony\Component\PropertyAccess\Exception\AccessException; | |
use Symfony\Component\PropertyAccess\PropertyAccess; | |
use Symfony\Component\PropertyAccess\PropertyAccessor; | |
class ApiTestCase extends KernelTestCase | |
{ | |
private static $staticClient; | |
/** | |
* @var History | |
*/ | |
private static $history; | |
/** | |
* @var Client | |
*/ | |
protected $client; | |
private $output; | |
/** | |
* @var PropertyAccessor | |
*/ | |
private $accessor; | |
public static function setUpBeforeClass() | |
{ | |
self::$staticClient = new Client([ | |
'base_url' => 'http://localhost:9003', | |
'defaults' => [ | |
'exceptions' => false | |
] | |
]); | |
self::$history = new History(); | |
self::$staticClient->getEmitter() | |
->attach(self::$history); | |
self::bootKernel(); | |
} | |
protected function setUp() | |
{ | |
$this->client = self::$staticClient; | |
$this->purgeDatabase(); | |
} | |
/** | |
* Clean up Kernel usage in this test. | |
*/ | |
protected function tearDown() | |
{ | |
// purposefully not calling parent class, which shuts down the kernel | |
} | |
/** | |
* Automatically prints the last response on a failure | |
*/ | |
protected function onNotSuccessfulTest(Exception $e) | |
{ | |
if (self::$history && $lastResponse = self::$history->getLastResponse()) { | |
$lastRequest = self::$history->getLastRequest(); | |
$this->printDebug(''); | |
$this->printDebug('<error>Failure!</error> when making the following request:'); | |
$this->printDebug(sprintf('<comment>%s</comment>: <info>%s</info>', $lastRequest->getMethod(), $lastRequest->getUrl())."\n"); | |
$this->debugResponse($lastResponse); | |
} | |
throw $e; | |
} | |
private function purgeDatabase() | |
{ | |
$purger = new ORMPurger($this->getService('doctrine.orm.default_entity_manager')); | |
$purger->purge(); | |
} | |
protected function getService($id) | |
{ | |
return self::$kernel->getContainer() | |
->get($id); | |
} | |
/** | |
* Prints the given response to the screen in a nice debug way | |
*/ | |
protected function debugResponse(ResponseInterface $response) | |
{ | |
$body = (string) $response->getBody(); | |
$contentType = $response->getHeader('Content-Type'); | |
if ($contentType == 'application/json' || strpos($contentType, '+json') !== false) { | |
$data = json_decode($body); | |
if ($data === null) { | |
// invalid JSON! | |
$this->printDebug($body); | |
} else { | |
// valid JSON, print it pretty | |
$this->printDebug(json_encode($data, JSON_PRETTY_PRINT)); | |
} | |
} else { | |
// the response is HTML - see if we should print all of it or some of it | |
$isValidHtml = strpos($body, '</body>') !== false; | |
if ($isValidHtml) { | |
$this->printDebug('<error>Failure!</error> Below is a summary of the HTML response from the server.'); | |
// finds the h1 and h2 tags and prints them only | |
$crawler = new Crawler($body); | |
$i = 1; | |
foreach ($crawler->filter('h1, h2')->extract(array('_text')) as $header) { | |
$this->printDebug(''); | |
$this->printDebug(sprintf( | |
' <comment>%s)</comment> %s', | |
$i, | |
trim($header) | |
)); | |
$i++; | |
} | |
} else { | |
$this->printDebug($body); | |
} | |
} | |
} | |
protected function printDebug($string) | |
{ | |
if ($this->output === null) { | |
$this->output = new ConsoleOutput(); | |
} | |
echo $string; | |
} | |
protected function assertResponsePropertiesExist(ResponseInterface $response, array $expectedProperties) | |
{ | |
foreach ($expectedProperties as $propertyPath) { | |
// this will blow up if the property doesn't exist | |
$this->readResponseProperty($response, $propertyPath); | |
} | |
} | |
protected function assertResponsePropertyExists(ResponseInterface $response, $propertyPath) | |
{ | |
// this will blow up if the property doesn't exist | |
$this->readResponseProperty($response, $propertyPath); | |
} | |
protected function assertResponsePropertyDoesNotExist(ResponseInterface $response, $propertyPath) | |
{ | |
try { | |
// this will blow up if the property doesn't exist | |
$this->readResponseProperty($response, $propertyPath); | |
$this->fail(sprintf('Property "%s" exists, but it should not', $propertyPath)); | |
} catch (RuntimeException $e) { | |
// cool, it blew up | |
// this catches all errors (but only errors) fro, the PropertyAccess component | |
} | |
} | |
protected function assertResponsePropertyEquals(ResponseInterface $response, $propertyPath, $expectedValue) | |
{ | |
$actual = $this->readResponseProperty($response, $propertyPath); | |
$this->assertEquals( | |
$expectedValue, | |
$actual, | |
sprintf( | |
'Property "%s": Expected "%s" but response was "%s"', | |
$propertyPath, | |
$expectedValue, | |
var_export($actual, true) | |
) | |
); | |
} | |
protected function assertResponsePropertyContains(ResponseInterface $response, $propertyPath, $expectedValue) | |
{ | |
$actualPropertValue = $this->readResponseProperty($response, $propertyPath); | |
$this->assertContains( | |
$expectedValue, | |
$actualPropertValue, | |
sprintf( | |
'Property "%s": Expected to contain "%s" but response was "%s"', | |
$propertyPath, | |
$expectedValue, | |
var_export($actualPropertValue, true) | |
) | |
); | |
} | |
protected function assertResponsePropertyIsArray(ResponseInterface $response, $propertyPath) | |
{ | |
$this->assertInternalType('array', $this->readResponseProperty($response, $propertyPath)); | |
} | |
protected function assertResponsePropertyCount(ResponseInterface $response, $propertyPath, $expectedCount) | |
{ | |
$this->assertCount((int) $expectedCount, $this->readResponseProperty($response, $propertyPath)); | |
} | |
/** | |
* @return EntityManager | |
*/ | |
protected function getEntityManager() | |
{ | |
return $this->getService('doctrine') | |
->getManager(); | |
} | |
private function readResponseProperty(ResponseInterface $response, $propertyPath) | |
{ | |
if ($this->accessor === null) { | |
$this->accessor = PropertyAccess::createPropertyAccessor(); | |
} | |
$data = json_decode((string)$response->getBody()); | |
try { | |
return $this->accessor->getValue($data, $propertyPath); | |
} catch (AccessException $e) { | |
// it could be a stdClass or an array of stdClass | |
$values = is_array($data) ? $data : get_object_vars($data); | |
throw new AccessException(sprintf( | |
'Error reading property "%s" from available keys (%s)', | |
$propertyPath, | |
implode(', ', array_keys($values)) | |
), 0, $e); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment