Skip to content

Commit

Permalink
Allow to make assertions with "max" prefix
Browse files Browse the repository at this point in the history
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
  • Loading branch information
henriquemoody committed Jul 14, 2018
1 parent ebd53d7 commit 6e4026d
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 7 deletions.
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,44 @@ If `length` is used without a suffix, this library will use [Equals][] to assert
Assert::length('something', 3);
```

### Max

Assertions can be executed with the `max` prefix which will assert the maximum
value of the input with the prefixed assertion:

```php
// will throw an exception => 3, the maximum of { 1, 2, 3 }, must be between 5 and 10
Assert::maxBetween([1, 2, 3], 5, 10);
```

As it can already be seen, the message shows not only the maximum of the input
that failed the assertion but also the input itself.

The `max` prefix can be used with any [iterable][] value:

```php
// will throw an exception => 3, the maximum of { 1, 2, 3 }, must be an even number
Assert::maxEven([1, 2, 3]);


// will throw an exception => 60, the maximum of `[traversable] (ArrayObject: { 45, 60, 20 })`, must be a valid perfect square
Assert::maxPerfectSquare(new ArrayObject([45, 60, 20]));
```

This library also allows you to use the `not` prefix after the `max` prefix:

```php
// will throw an exception => 23, the maximum of { 23, 7, 20 }, must not be positive
Assert::maxNotPositive([23, 7, 20]);
```

If `max` is used without a suffix, this library will use [Equals][] to assert:

```php
// will throw an exception => "C", the maximum of { "A", "B", "C" }, must be equals "D"
Assert::max(['A', 'B', 'C'], 'D');
```

### Min

Assertions can be executed with the `min` prefix which will assert the minimum
Expand Down Expand Up @@ -194,7 +232,6 @@ Assert::min(['A', 'B', 'C'], 'D');

## To-do

- Allow to make assertions with `max` prefix
- Allow to make assertions with `nullOr` prefix
- Allow to make assertions with `any` prefix
- Allow to make assertions with `key` prefix
Expand Down
41 changes: 35 additions & 6 deletions src/Assert.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use finfo;
use Respect\Assertion\Assertor\AllAssertor;
use Respect\Assertion\Assertor\LengthAssertor;
use Respect\Assertion\Assertor\MaxAssertor;
use Respect\Assertion\Assertor\MinAssertor;
use Respect\Assertion\Creator\ComposedCreator;
use Respect\Assertion\Creator\NotCreator;
Expand Down Expand Up @@ -353,7 +354,32 @@
* @method static void lengthPrimeNumber($input, $description = null)
* @method static void lowercase($input, $description = null)
* @method static void macAddress($input, $description = null)
* @method static void max($input, $maxValue, bool $inclusive = true, $description = null)
* @method static void max(iterable $input, $compareTo, $description = null)
* @method static void maxBetween(iterable $input, $min = null, $max = null, bool $inclusive = true, $description = null)
* @method static void maxEven(iterable $input, $description = null)
* @method static void maxFactor(iterable $input, int $dividend, $description = null)
* @method static void maxFibonacci(iterable $input, $description = null)
* @method static void maxIdentical(iterable $input, $value, $description = null)
* @method static void maxMax(iterable $input, $maxValue, bool $inclusive = true, $description = null)
* @method static void maxMin(iterable $input, $minValue, bool $inclusive = true, $description = null)
* @method static void maxMultiple(iterable $input, int $multipleOf, $description = null)
* @method static void maxNotBetween(iterable $input, $min = null, $max = null, bool $inclusive = true, $description = null)
* @method static void maxNotEquals(iterable $input, $compareTo, $description = null)
* @method static void maxNotEven(iterable $input, $description = null)
* @method static void maxNotFactor(iterable $input, int $dividend, $description = null)
* @method static void maxNotFibonacci(iterable $input, $description = null)
* @method static void maxNotIdentical(iterable $input, $value, $description = null)
* @method static void maxNotMax(iterable $input, $maxValue, bool $inclusive = true, $description = null)
* @method static void maxNotMin(iterable $input, $minValue, bool $inclusive = true, $description = null)
* @method static void maxNotMultiple(iterable $input, int $multipleOf, $description = null)
* @method static void maxNotOdd(iterable $input, $description = null)
* @method static void maxNotPerfectSquare(iterable $input, $description = null)
* @method static void maxNotPositive(iterable $input, $description = null)
* @method static void maxNotPrimeNumber(iterable $input, $description = null)
* @method static void maxOdd(iterable $input, $description = null)
* @method static void maxPerfectSquare(iterable $input, $description = null)
* @method static void maxPositive(iterable $input, $description = null)
* @method static void maxPrimeNumber(iterable $input, $description = null)
* @method static void mimetype($input, string $mimetype, $description = null)
* @method static void min(iterable $input, $compareTo, $description = null)
* @method static void minBetween(iterable $input, $min = null, $max = null, bool $inclusive = true, $description = null)
Expand Down Expand Up @@ -561,13 +587,16 @@ private static function getAssertionCreator(): AssertionCreator
{
if (!self::$assertionCreator instanceof AssertionCreator) {
self::$assertionCreator = new ComposedCreator(
new MinAssertor(),
new MaxAssertor(),
new ComposedCreator(
new LengthAssertor(),
new MinAssertor(),
new ComposedCreator(
new AllAssertor(),
new NotCreator(
new StandardCreator()
new LengthAssertor(),
new ComposedCreator(
new AllAssertor(),
new NotCreator(
new StandardCreator()
)
)
)
)
Expand Down
77 changes: 77 additions & 0 deletions src/Assertor/MaxAssertor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

/*
* This file is part of Respect/Assertion.
*
* (c) Henrique Moody <henriquemoody@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

declare(strict_types=1);

namespace Respect\Assertion\Assertor;

use BadMethodCallException;
use Respect\Assertion\Assertion;
use Respect\Assertion\Assertor;
use Respect\Validation\Exceptions\ValidationException;
use function is_array;
use function is_iterable;
use function iterator_to_array;
use function max;
use function str_replace;

final class MaxAssertor implements Assertor
{
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'max';
}

/**
* {@inheritdoc}
*/
public function execute(Assertion $assertion, $input): void
{
if (!is_iterable($input)) {
throw new BadMethodCallException('Assertion with "max" prefix must be iterable');
}

try {
$assertion->assert($this->getMax($input));
} catch (ValidationException $exception) {
throw $this->getCustomizedException($input, $exception);
}
}

private function getMax(iterable $input)
{
if (is_array($input)) {
return max($input);
}

return $this->getMax(iterator_to_array($input));
}

private function getCustomizedException($asserted, ValidationException $exception): ValidationException
{
$exception->setParam('asserted', $asserted);
if ($exception->hasCustomTemplate()) {
return $exception;
}

$exception->setTemplate(
str_replace(
'{{name}}', '{{name}}, the maximum of {{asserted}},',
$exception->getTemplate()
)
);

return $exception;
}
}
6 changes: 6 additions & 0 deletions tests/integration/AssertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public function validProvider(): array
['length', ['something', 9]],
['lengthBetween', ['something', 1, 9]],
['lengthNot', ['something', 2]],
['max', [[1, 2, 3], 3]],
['maxPositive', [[1, 2, 3]]],
['maxNot', [[1, 2, 3], 1]],
['min', [[1, 2, 3], 1]],
['minPositive', [[1, 2, 3]]],
['minNot', [[1, 2, 3], 3]],
Expand Down Expand Up @@ -75,6 +78,9 @@ public function invalidProvider(): array
['allEven', [[2, 4, 5]]],
['length', ['something', 2]],
['lengthNotPositive', ['something']],
['max', [[1, 2, 3], 2]],
['maxPositive', [[-1, -2, -3]]],
['maxNotPositive', [[1, 2, 3]]],
['min', [[1, 2, 3], 3]],
['minPositive', [[-1, -2, -3]]],
['minNot', [[1, 2, 3], 1]],
Expand Down
163 changes: 163 additions & 0 deletions tests/unit/Assertor/MaxAssertorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

/*
* This file is part of Respect/Assertion.
*
* (c) Henrique Moody <henriquemoody@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

declare(strict_types=1);

namespace Respect\Test\Unit\Assertion\Assertor;

use ArrayObject;
use BadMethodCallException;
use DomainException;
use PHPUnit\Framework\TestCase;
use Respect\Assertion\Assertion;
use Respect\Assertion\Assertor\MaxAssertor;
use Respect\Validation\Exceptions\AlwaysInvalidException;
use function range;

/**
* @covers \Respect\Assertion\Assertor\MaxAssertor
*
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class MaxAssertorTest extends TestCase
{
/**
* @var MaxAssertor
*/
private $sut;

/**
* {@inheritdoc}
*/
protected function setUp(): void
{
$this->sut = new MaxAssertor();
}

/**
* @test
*/
public function itShouldReturnTheAssertorName(): void
{
self::assertEquals('max', $this->sut->getName());
}

/**
* @test
*/
public function itShouldThrowAnExceptionWhenInputIsNotIterable(): void
{
$this->expectException(BadMethodCallException::class);

$this->sut->execute($this->createMock(Assertion::class), 42);
}

/**
* @test
*/
public function itShouldAssertTheMaxValueOfTheInputWhenItIsAnArray(): void
{
$input = range(100, 105);

$assertion = $this->createMock(Assertion::class);
$assertion
->expects($this->once())
->method('assert')
->with(105);

$this->sut->execute($assertion, $input);
}

/**
* @test
*/
public function itShouldAssertTheMaxValueOfTheInputWhenItIsAnIterableValue(): void
{
$input = new ArrayObject(range(100, 200));

$assertion = $this->createMock(Assertion::class);
$assertion
->expects($this->once())
->method('assert')
->with(200);

$this->sut->execute($assertion, $input);
}

/**
* @test
*/
public function itShouldThrowCustomExceptionsWhenAssertionFails(): void
{
$input = [1, 2, 3];

$exception = new DomainException('Custom exception');

$assertion = $this->createMock(Assertion::class);
$assertion
->expects($this->once())
->method('assert')
->with(3)
->willThrowException($exception);

$this->expectExceptionObject($exception);

$this->sut->execute($assertion, $input);
}

/**
* @test
*/
public function itShouldThrowAndModifyValidationExceptionsWhenAssertionFails(): void
{
$input = [1, 2, 3];

$exception = new AlwaysInvalidException();
$exception->configure('something');

$assertion = $this->createMock(Assertion::class);
$assertion
->expects($this->once())
->method('assert')
->with(3)
->willThrowException($exception);

self::assertEquals('something is always invalid', $exception->getMessage());

$this->expectException(AlwaysInvalidException::class);
$this->expectExceptionMessage('something, the maximum of { 1, 2, 3 }, is always invalid');

$this->sut->execute($assertion, $input);
}

/**
* @test
*/
public function itShouldThrowAndNotModifyValidationExceptionsWhenAssertionFailsAndExceptionHasCustomTemplate(): void
{
$input = [1, 2, 3];

$exception = new AlwaysInvalidException();
$exception->configure('something');
$exception->setTemplate('{{input}} is something not cool');

$assertion = $this->createMock(Assertion::class);
$assertion
->expects($this->once())
->method('assert')
->with(3)
->willThrowException($exception);

$this->expectExceptionObject($exception);

$this->sut->execute($assertion, $input);
}
}

0 comments on commit 6e4026d

Please sign in to comment.