Skip to content

Commit

Permalink
feat(json): adds checks for json formats
Browse files Browse the repository at this point in the history
  • Loading branch information
pitchart committed Feb 12, 2021
1 parent 915b63b commit f5af183
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 3 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"phpunit/phpunit": "8.5",
"phpcollection/phpcollection": "^0.5.0",
"psr/http-message": "^1.0",
"pitchart/transformer": "^1.6"
"pitchart/transformer": "^1.6",
"justinrainbow/json-schema": "^5.2"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
<phpunit bootstrap="tests/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
Expand Down
60 changes: 60 additions & 0 deletions src/Checks/JsonCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php


namespace Pitchart\Phlunit\Checks;


use PHPUnit\Framework\Assert;
use Pitchart\Phlunit\Checks\Mixin\ConstraintCheck;
use Pitchart\Phlunit\Checks\Mixin\TypeCheck;
use Pitchart\Phlunit\Checks\Mixin\WithMessage;
use Pitchart\Phlunit\Constraint\Json\MatchesSchema;

class JsonCheck implements FluentCheck
{
use WithMessage, ConstraintCheck, TypeCheck;

private $value;

/**
* JsonCheck constructor.
*
* @param $value
*/
public function __construct(string $value)
{
Assert::assertJson($value);
$this->value = $value;
}

public function isEqualTo(string $expected): self
{
Assert::assertJsonStringEqualsJsonString($expected, $this->value, $this->message);
return $this;
}

public function isNotEqualTo(string $expected): self
{
Assert::assertJsonStringNotEqualsJsonString($expected, $this->value, $this->message);
return $this;
}

public function isEqualToFileContent(string $path): self
{
Assert::assertJsonStringEqualsJsonFile($path, $this->value, $this->message);
return $this;
}

public function isNotEqualToFileContent(string $path): self
{
Assert::assertJsonStringNotEqualsJsonFile($path, $this->value, $this->message);
return $this;
}

public function matchesSchema($schema)
{
Assert::assertThat($this->value, new MatchesSchema($schema), $this->message);
return $this;
}

}
62 changes: 62 additions & 0 deletions src/Constraint/Json/MatchesSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php


namespace Pitchart\Phlunit\Constraint\Json;


use JsonSchema\Validator;
use PHPUnit\Framework\Constraint\Constraint;

class MatchesSchema extends Constraint
{
private $schema;

public function __construct($schema)
{
$this->schema = $this->convertToObject($schema);
}


public function toString(): string
{
return sprintf('matches Json Schema %s', json_encode($this->schema));
}

/**
* @inheritdoc
*/
protected function matches($other): bool
{
$other = $this->convertToObject($other);

$validator = new Validator();
$validator->check($other, $this->schema);

return $validator->isValid();
}

/**
* @inheritdoc
*/
protected function additionalFailureDescription($other): string
{
$other = $this->convertToObject($other);

$validator = new Validator();
$validator->check($other, $this->schema);

return implode("\n", array_map(function ($error) {
return sprintf("[%s] %s", $error['property'], $error['message']);
}, $validator->getErrors()));
}

private function convertToObject($value): \stdClass
{
if (is_string($value)) {
return json_decode($value);
}

return json_decode(json_encode($value));
}

}
64 changes: 64 additions & 0 deletions tests/Constraint/Json/MatchesSchemaTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Tests\Pitchart\Phlunit\Constraint\Json;

use Pitchart\Phlunit\Constraint\Json\MatchesSchema;
use PHPUnit\Framework\TestCase;
use Tests\Pitchart\Phlunit\Constraint\ConstraintTestCase;
use Tests\Pitchart\Phlunit\Fixture\Person;

class MatchesSchemaTest extends ConstraintTestCase
{
/**
* @var MatchesSchema
*/
protected $constraint;

public function setUp(): void
{
$this->constraint = new MatchesSchema(['type' => 'object', 'required' => ['name'], 'properties' => ['name' => ['type' => 'string']]]);
}

public function test_succeeds_when_sut_is_a_string_matching_provided_format()
{
$this->assertTrue($this->constraint->evaluate('{"name": "Batman"}', '', true));
}

public function test_succeeds_when_sut_is_an_array_matching_provided_format()
{
$this->assertTrue($this->constraint->evaluate(['name' => 'Batman'], '', true));
}

public function test_succeeds_when_sut_is_an_object_matching_provided_format()
{
$this->assertTrue($this->constraint->evaluate(new Person('Batman'), '', true));
}

public function test_succeeds_when_sut_is_is_more_than_provided_format()
{
$this->assertTrue($this->constraint->evaluate(['name' => 'Batman', 'city' => 'Gotham City'], '', true));
}

public function test_fails_when_sut_has_a_property_missing()
{
$this->assertFalse($this->constraint->evaluate(['lastname' => 'Batman'], '', true));
}

public function test_fails_when_sut_do_not_respect_format()
{
$this->assertFalse($this->constraint->evaluate(['name' => null], '', true));
}

public function test_fails_with_a_clear_and_complete_error_message()
{
$json = '{"name":null}';

$this->assertHasFailingMessage(
<<<EOF
Failed asserting that '{"name":null}' matches Json Schema {"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}.
[name] NULL value found, but a string is required
EOF
, $json);
}

}
7 changes: 6 additions & 1 deletion tests/Fixture/Person.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Tests\Pitchart\Phlunit\Fixture;


class Person
class Person implements \JsonSerializable
{
private $name;

Expand All @@ -18,5 +18,10 @@ public function __construct($name)
$this->name = $name;
}

function jsonSerialize()
{
return ['name' => $this->name];
}


}
3 changes: 3 additions & 0 deletions tests/Fixture/files/batman.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "Batman"
}
3 changes: 3 additions & 0 deletions tests/Fixture/files/robin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name":"Robin"
}
37 changes: 37 additions & 0 deletions tests/JsonCheckTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php


namespace Tests\Pitchart\Phlunit;


use PHPUnit\Framework\TestCase;
use Pitchart\Phlunit\Check;
use Pitchart\Phlunit\Checks\JsonCheck;

class JsonCheckTest extends TestCase
{
/**
* @param string $sut
* @param string $method
* @param array $arguments
* @dataProvider methodsAreFulentProvider
*/
public function test_has_fluent_methods(string $sut, string $method, array $arguments = [])
{
Check::that(call_user_func_array([Check::that($sut)->asJson(), $method], $arguments))
->isAnInstanceOf(JsonCheck::class);
}

public function methodsAreFulentProvider()
{
$batman = file_get_contents(TEST_FILES_PATH.'batman.json');
yield from [
'isEqualTo' => ['{"name":"Batman"}', 'isEqualTo', [$batman]],
'isNotEqualTo' => ['{"name":"Batman"}', 'isNotEqualTo', ['{"name":"Robin"}']],
'isEqualToFileContent' => ['{"name":"Batman"}', 'isEqualToFileContent', [TEST_FILES_PATH.'batman.json']],
'isNotEqualToFileContent' => ['{"name":"Batman"}', 'isNotEqualToFileContent', [TEST_FILES_PATH.'robin.json']],
'matchesSchema' => ['{"name":"Batman"}', 'matchesSchema', [['type' => 'object', 'required' => ['name'], 'properties' => ['name' => ['type' => 'string']]],
]]
];
}
}
7 changes: 7 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php declare(strict_types=1);

if (!\defined('TEST_FILES_PATH')) {
\define('TEST_FILES_PATH', __DIR__ . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR. 'files' . \DIRECTORY_SEPARATOR);
}

require_once __DIR__ . '/../vendor/autoload.php';

0 comments on commit f5af183

Please sign in to comment.