Created
October 25, 2022 18:28
-
-
Save DominikStyp/8df177688ec1598984128d59fc63797b to your computer and use it in GitHub Desktop.
PHPUnit: Asserting JSON Paths
This file contains 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\Http; | |
/** | |
* | |
* Use this trait in your Laravel/Lumen TestCase class | |
* to get the assertJSON... functionality out of the box | |
* | |
* Trait CanAssertJsonPaths | |
* @package Tests\Http | |
*/ | |
trait CanAssertJsonPaths | |
{ | |
protected static $uriCachedData = []; | |
/** | |
* Visit the given URI with a JSON request. | |
* | |
* @param string $uri - endpoint uri ex. '/api/user/1' | |
* @param array $data | |
* @param array $headers | |
* @return $this | |
*/ | |
public function cachedJsonGet(string $uri, array $data = [], array $headers = []) | |
{ | |
if (empty(static::$uriCachedData[$uri])) { | |
$this->json('GET', $uri, $data, $headers); | |
static::$uriCachedData[$uri] = $this->response; | |
return $this; | |
} else { | |
$this->response = static::$uriCachedData[$uri]; | |
return $this; | |
} | |
} | |
public function clearCacheForUri(string $uri) | |
{ | |
if(!empty(static::$uriCachedData[$uri])){ | |
unset(static::$uriCachedData[$uri]); | |
} | |
} | |
/** | |
* @param string $uri - endpoint uri ex. '/api/user/1' | |
* @param bool $cached | |
* @return false|string | |
*/ | |
protected function getContentResponseFromUri(string $uri, bool $cached = false) | |
{ | |
if ($cached) { | |
$response = $this->cachedJsonGet($uri)->response->getContent(); | |
} else { | |
$response = $this->json('GET', $uri)->response->getContent(); | |
} | |
return $response; | |
} | |
/** | |
* @param string $uri - endpoint uri ex. '/api/user/1' | |
* @param string $jsonPath - ex. 'data.users[1].name' | |
* @param bool $cached - should endpoint result be cached across tests | |
* @throws \JsonException | |
*/ | |
protected function assertJsonPathExists(string $uri, string $jsonPath, bool $cached = false) | |
{ | |
$jsonPath = $this->clearJSONPathAndChangeItToPHPSyntax($jsonPath); | |
$content = $this->getContentResponseFromUri($uri, $cached); | |
$contentDecoded = json_decode($content, false); | |
$pathToCheck = '$contentDecoded->' . $jsonPath; | |
$isset = eval('return isset(' . $pathToCheck . ');'); | |
$this->assertTrue($isset, "Error in endpoint: {$uri} \n {$pathToCheck} path doesn't exist in JSON"); | |
} | |
/** | |
* @param string $uri - endpoint uri ex. '/api/user/1' | |
* @param string $jsonPath - ex. 'data.users[1].name' | |
* @param bool $cached - should endpoint result be cached across tests | |
* @throws \JsonException | |
*/ | |
protected function assertJsonPathDoesntExist(string $uri, string $jsonPath, bool $cached = false) | |
{ | |
$jsonPath = $this->clearJSONPathAndChangeItToPHPSyntax($jsonPath); | |
$content = $this->getContentResponseFromUri($uri, $cached); | |
$contentDecoded = json_decode($content, false); | |
$pathToCheck = '$contentDecoded->' . $jsonPath; | |
$isNotSet = eval('return !isset(' . $pathToCheck . ');'); | |
$valueOfTheKey = ""; | |
if (!$isNotSet) { | |
$valueOfTheKey = eval('return json_encode(' . $pathToCheck . ');'); | |
} | |
$this->assertTrue($isNotSet, | |
"Error in endpoint: {$uri} \n{$pathToCheck}path exists in JSON, value is: {$valueOfTheKey}"); | |
} | |
/** | |
* Is empty means: [], null, 0, "", false, !isset() | |
* | |
* @param string $uri - endpoint uri ex. '/api/user/1' | |
* @param string $jsonPath - ex. 'data.users[1].name' | |
* @param bool $cached - should endpoint result be cached across tests in class (first invoke is stored in cache | |
* and served to the rest invokes) | |
*/ | |
protected function assertJsonPathIsEmpty(string $uri, string $jsonPath, bool $cached = false) | |
{ | |
$jsonPath = $this->clearJSONPathAndChangeItToPHPSyntax($jsonPath); | |
$content = $this->getContentResponseFromUri($uri, $cached); | |
$contentDecoded = json_decode($content, false); | |
$pathToCheck = '$contentDecoded->' . $jsonPath; | |
$empty = eval('return empty(' . $pathToCheck . ');'); | |
$valueOfTheKey = ""; | |
if (!$empty) { | |
$valueOfTheKey = eval('return json_encode(' . $pathToCheck . ');'); | |
} | |
$this->assertTrue($empty, | |
"Error in endpoint: {$uri} \n{$pathToCheck} path is not empty, so does not equal: [], null, 0, \"\", false, or !isset() \nValue of the key: {$valueOfTheKey}"); | |
} | |
/** | |
* @param string $uri - endpoint uri ex. '/api/user/1' | |
* @param string $jsonPath - ex. 'data.users[1].name' | |
* @param string|int|float $expectedValue | |
* @param bool $cached - should endpoint result be cached across tests in class (first invoke is stored in cache | |
* and served to the rest invokes) | |
* @throws \JsonException | |
*/ | |
protected function assertJsonPathHasValue(string $uri, string $jsonPath, $expectedValue, bool $cached = false) | |
{ | |
$this->assertJsonPathExists($uri, $jsonPath, $cached); | |
$content = $this->getContentResponseFromUri($uri, $cached); | |
$contentDecoded = json_decode($content, false); | |
$jsonPath = $this->clearJSONPathAndChangeItToPHPSyntax($jsonPath); | |
$pathToCheck = '$contentDecoded->' . $jsonPath; | |
$value = eval('return ' . $pathToCheck . ';'); | |
if (is_string($expectedValue) && preg_replace("#\s+#", "", $expectedValue) === '[]') { | |
$this->assertTrue(is_array($value), "{$pathToCheck} path is not an array"); | |
} else { | |
$this->assertEquals($expectedValue, $value, | |
"Error in endpoint: {$uri} \n{$pathToCheck} path doesn't have expected value: {$expectedValue}, instead it equals: {$value}"); | |
} | |
} | |
/** | |
* @param string $uri - endpoint uri ex. '/api/user/1' | |
* @param string $jsonPath - ex. 'data.users[1].name' | |
* @param string $expectedRegex - ex. '#\d+#' ( # is regex delimiter in the example ) | |
* @param bool $cached - should endpoint result be cached across tests in class (first invoke is stored in cache | |
* and served to the rest invokes) | |
* @throws \JsonException | |
*/ | |
protected function assertJsonPathHasRegex(string $uri, string $jsonPath, string $expectedRegex, bool $cached = false) | |
{ | |
$this->assertJsonPathExists($uri, $jsonPath, $cached); | |
$content = $this->getContentResponseFromUri($uri, $cached); | |
$contentDecoded = json_decode($content, false); | |
$jsonPath = $this->clearJSONPathAndChangeItToPHPSyntax($jsonPath); | |
$pathToCheck = '$contentDecoded->' . $jsonPath; | |
$value = eval('return ' . $pathToCheck . ';'); | |
if (preg_replace("#\s+#", "", $expectedRegex) === '[]') { | |
$this->assertTrue(is_array($value), "$pathToCheck path is not an array"); | |
} else { | |
$this->assertRegExp($expectedRegex, strval($value), | |
"Error in endpoint: {$uri} \n$pathToCheck path doesn't have expected value: {$expectedRegex}, instead it equals: {$value}"); | |
} | |
} | |
/** | |
* @param string $jsonPath - ex. 'data.users[1].name' | |
* @return string | |
*/ | |
private function clearJSONPathAndChangeItToPHPSyntax(string $jsonPath): string | |
{ | |
// clear from JSON not-allowed chars | |
$jsonPath = preg_replace('#[^a-zA-Z0-9\"\]\[._ -]+#', '', $jsonPath); | |
// replace 'data.user[1].name' to 'data->user[1]->name' which is valid PHP syntax | |
$jsonPath = str_replace('.', '->', $jsonPath); | |
return $jsonPath; | |
} | |
} |
This file contains 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\Http; | |
class CatalogEndpointTest extends TestCase | |
{ | |
public function testCatalogBrowser() | |
{ | |
$uri = '/api/v1/catalog/browser?category_slug=some-slug'; | |
$this->assertJsonPathHasValue($uri, 'data.items[0].product.alias', 'Training'); | |
$this->assertJsonPathHasValue($uri, 'data.items[1].product.alias', 'Training'); | |
$this->assertJsonPathHasRegex($uri, 'data.items[0].product.alias.id', self::UUID_REGEX, true); | |
} | |
} | |
This file contains 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\Http; | |
abstract class TestCase extends \Laravel\Lumen\Testing\TestCase | |
{ | |
use CanAssertJsonPaths; | |
// ... | |
// ... | |
const ID_REGEXP = "#[A-Z0-9-]{36}#"; | |
const DATE_TIME_REGEX = "#\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}#"; | |
const CURRENCY_CODE_REGEXP = "#[A-Z]{2,5}#"; | |
const ALIAS_REGEX = '#[A-Za-z0-9-]{3,}#si'; | |
const TITLE_REGEX = "#\w{3,}#"; | |
const UUID_REGEX = "#[A-Z0-9-]{20,}#"; | |
const SLUG_REGEX = '#[a-zA-Z0-9_-]+#'; | |
const PRODUCT_CODE_REGEX = "/[A-Za-z0-9#-_]{2,}.*?/"; | |
const FLOAT_REGEX = '#^[0-9.]{1,}$#'; | |
const INT_REGEX = '#^[0-9]{1,}$#'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment