mirror of
https://github.com/Respect/Validation.git
synced 2024-06-29 10:40:24 +02:00
Improve how we handle data providers
We frequently repeat ourselves in the data providers. There's a clear advantage to that, as it increases the number of possibilities with the test, but it can also be annoying when we have to repeat ourselves. Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
parent
28b3f88627
commit
b2250c65a0
167
tests/fixtures/data-provider.php
vendored
Normal file
167
tests/fixtures/data-provider.php
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Respect\Validation\Test\Stubs\WithProperties;
|
||||
use Respect\Validation\Test\Stubs\WithStaticProperties;
|
||||
use Respect\Validation\Test\Stubs\WithUninitialized;
|
||||
|
||||
return [
|
||||
'null' => [
|
||||
'value' => [null],
|
||||
'tags' => ['nullType', 'empty', 'undefined'],
|
||||
],
|
||||
|
||||
// BooleanTypes
|
||||
'false' => [
|
||||
'value' => [false],
|
||||
'tags' => ['boolType', 'false', 'empty'],
|
||||
],
|
||||
'true' => [
|
||||
'value' => [true],
|
||||
'tags' => ['boolType', 'true'],
|
||||
],
|
||||
|
||||
// IntegerTypes
|
||||
'zero integer' => [
|
||||
'value' => [0],
|
||||
'tags' => ['intType', 'zero'],
|
||||
],
|
||||
'positive integer' => [
|
||||
'value' => [PHP_INT_MAX],
|
||||
'tags' => ['intType', 'positive'],
|
||||
],
|
||||
'negative integer' => [
|
||||
'value' => [PHP_INT_MIN],
|
||||
'tags' => ['intType', 'negative'],
|
||||
],
|
||||
|
||||
// StringTypes
|
||||
'string' => [
|
||||
'value' => ['string'],
|
||||
'tags' => ['stringType'],
|
||||
],
|
||||
'empty string' => [
|
||||
'value' => [''],
|
||||
'tags' => ['stringType', 'empty', 'undefined'],
|
||||
],
|
||||
'integer string' => [
|
||||
'value' => ['500'],
|
||||
'tags' => ['stringType', 'intVal', 'positive'],
|
||||
],
|
||||
'float string' => [
|
||||
'value' => ['56.8'],
|
||||
'tags' => ['stringType', 'floatVal', 'positive'],
|
||||
],
|
||||
'zero string' => [
|
||||
'value' => ['0'],
|
||||
'tags' => ['stringType', 'intVal', 'zero'],
|
||||
],
|
||||
|
||||
// Float types
|
||||
'zero float' => [
|
||||
'value' => [0.0],
|
||||
'tags' => ['floatType', 'zero'],
|
||||
],
|
||||
'positive float' => [
|
||||
'value' => [32.890],
|
||||
'tags' => ['floatType', 'positive'],
|
||||
],
|
||||
'negative float' => [
|
||||
'value' => [-893.1],
|
||||
'tags' => ['floatType', 'negative'],
|
||||
],
|
||||
|
||||
// Array types
|
||||
'array list' => [
|
||||
'value' => [[4, 5, 6]],
|
||||
'tags' => ['arrayType', 'iterableType', 'countable'],
|
||||
],
|
||||
'array associative with string keys' => [
|
||||
'value' => [['broccoli' => 89, 'spinach' => 123, 'beets' => 90]],
|
||||
'tags' => ['arrayType', 'iterableType', 'countable'],
|
||||
],
|
||||
'array associative with int keys' => [
|
||||
'value' => [[1 => 'cauliflower', 2 => 'eggplant', 3 => 'asparagus']],
|
||||
'tags' => ['arrayType', 'iterableType', 'countable'],
|
||||
],
|
||||
'empty array' => [
|
||||
'value' => [[]],
|
||||
'tags' => ['arrayType', 'iterableType', 'countable', 'empty'],
|
||||
],
|
||||
|
||||
// Array values
|
||||
'ArrayObject' => [
|
||||
'value' => [new ArrayObject([1, 2, 3])],
|
||||
'tags' => ['objectType', 'iterableType', 'ArrayObject', 'countable'],
|
||||
],
|
||||
'empty ArrayObject' => [
|
||||
'value' => [new ArrayObject([])],
|
||||
'tags' => ['objectType', 'iterableType', 'ArrayObject', 'countable', 'empty'],
|
||||
],
|
||||
|
||||
// Iterable types
|
||||
'generator' => [
|
||||
'value' => [(static fn() => yield 7)()], // phpcs:ignore
|
||||
'tags' => ['objectType', 'iterableType', 'generator'],
|
||||
],
|
||||
'empty generator' => [
|
||||
'value' => [(static fn() => yield from [])()],
|
||||
'tags' => ['objectType', 'iterableType', 'generator', 'empty'],
|
||||
],
|
||||
|
||||
// Callable types
|
||||
'closure' => [
|
||||
'value' => [static fn() => 'foo'],
|
||||
'tags' => ['objectType', 'callable'],
|
||||
],
|
||||
|
||||
// Object types
|
||||
'object' => [
|
||||
'value' => [new stdClass()],
|
||||
'tags' => ['objectType'],
|
||||
],
|
||||
'object with properties' => [
|
||||
'value' => [new WithProperties()],
|
||||
'tags' => ['objectType'],
|
||||
],
|
||||
'object with uninitialized properties' => [
|
||||
'value' => [new WithUninitialized()],
|
||||
'tags' => ['objectType'],
|
||||
],
|
||||
'object with static properties' => [
|
||||
'value' => [new WithStaticProperties()],
|
||||
'tags' => ['objectType'],
|
||||
],
|
||||
|
||||
// Resource types
|
||||
'stream-context resource' => [
|
||||
'value' => [stream_context_create()],
|
||||
'tags' => ['resourceType'],
|
||||
],
|
||||
'stream resource' => [
|
||||
'value' => [tmpfile()],
|
||||
'tags' => ['resourceType'],
|
||||
],
|
||||
|
||||
// Closed resource (is not a resourceType)
|
||||
'resource (closed)' => [
|
||||
'value' => [
|
||||
(static function () {
|
||||
$resource = tmpfile();
|
||||
if ($resource === false) {
|
||||
throw new RuntimeException('Failed to create temporary file.');
|
||||
}
|
||||
fclose($resource);
|
||||
|
||||
return $resource;
|
||||
})(),
|
||||
],
|
||||
'tags' => ['closedResource'],
|
||||
],
|
||||
];
|
64
tests/library/DataProvider.php
Normal file
64
tests/library/DataProvider.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Test;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function array_filter;
|
||||
use function array_intersect;
|
||||
use function array_map;
|
||||
|
||||
/**
|
||||
* @implements IteratorAggregate<array<array{mixed}>>
|
||||
*/
|
||||
final class DataProvider implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $data
|
||||
) {
|
||||
}
|
||||
|
||||
public function with(string ...$tags): self
|
||||
{
|
||||
return new self(array_filter(
|
||||
$this->data,
|
||||
static fn($value) => array_intersect($tags, $value['tags']) === $tags
|
||||
));
|
||||
}
|
||||
|
||||
public function withAny(string ...$tags): self
|
||||
{
|
||||
return new self(array_filter(
|
||||
$this->data,
|
||||
static fn($value) => array_intersect($tags, $value['tags']) !== []
|
||||
));
|
||||
}
|
||||
|
||||
public function without(string ...$tags): self
|
||||
{
|
||||
return new self(array_filter(
|
||||
$this->data,
|
||||
static fn($value) => array_intersect($tags, $value['tags']) === []
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<array<mixed>>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator(array_map(static fn(array $value) => $value['value'], $this->data));
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Test\DataProvider;
|
||||
|
||||
trait UndefinedProvider
|
||||
{
|
||||
/**
|
||||
* @return mixed[][]
|
||||
*/
|
||||
public static function providerForUndefined(): array
|
||||
{
|
||||
return [
|
||||
[null],
|
||||
[''],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[][]
|
||||
*/
|
||||
public static function providerForNotUndefined(): array
|
||||
{
|
||||
return [
|
||||
[0],
|
||||
[0.0],
|
||||
['0'],
|
||||
[false],
|
||||
[true],
|
||||
[' '],
|
||||
[[]],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Test;
|
||||
|
||||
use ArrayObject;
|
||||
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
|
||||
use Respect\Validation\Test\Stubs\WithProperties;
|
||||
use Respect\Validation\Test\Stubs\WithStaticProperties;
|
||||
|
@ -17,7 +16,6 @@ use Respect\Validation\Test\Stubs\WithUninitialized;
|
|||
use Respect\Validation\Validatable;
|
||||
use stdClass;
|
||||
|
||||
use function array_merge;
|
||||
use function implode;
|
||||
use function ltrim;
|
||||
use function realpath;
|
||||
|
@ -25,10 +23,6 @@ use function Respect\Stringifier\stringify;
|
|||
use function sprintf;
|
||||
use function strrchr;
|
||||
use function substr;
|
||||
use function tmpfile;
|
||||
|
||||
use const PHP_INT_MAX;
|
||||
use const PHP_INT_MIN;
|
||||
|
||||
abstract class TestCase extends PHPUnitTestCase
|
||||
{
|
||||
|
@ -72,178 +66,109 @@ abstract class TestCase extends PHPUnitTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/** @return array<array{mixed}> */
|
||||
public static function providerForAnyValues(): array
|
||||
public static function providerForAnyValues(): DataProvider
|
||||
{
|
||||
return array_merge(
|
||||
self::providerForStringTypes(),
|
||||
self::providerForNonScalarValues(),
|
||||
self::providerForEmptyIterableValues(),
|
||||
self::providerForNonEmptyIterableValues(),
|
||||
self::providerForNonIterableTypes(),
|
||||
self::providerForIntegerTypes(),
|
||||
self::providerForBooleanTypes(),
|
||||
self::providerForFloatTypes(),
|
||||
);
|
||||
return new DataProvider((include __DIR__ . '/../fixtures/data-provider.php'));
|
||||
}
|
||||
|
||||
/** @return array<array{scalar}> */
|
||||
public static function providerForScalarValues(): array
|
||||
public static function providerForScalarValues(): DataProvider
|
||||
{
|
||||
return array_merge(
|
||||
self::providerForStringTypes(),
|
||||
self::providerForIntegerTypes(),
|
||||
self::providerForBooleanTypes(),
|
||||
self::providerForFloatTypes(),
|
||||
);
|
||||
return self::providerForAnyValues()->withAny('stringType', 'boolType', 'intType', 'floatType');
|
||||
}
|
||||
|
||||
/** @return array<array{scalar}> */
|
||||
public static function providerForEmptyScalarValues(): array
|
||||
public static function providerForEmptyScalarValues(): DataProvider
|
||||
{
|
||||
return [
|
||||
'empty string' => [''],
|
||||
'false' => [false],
|
||||
];
|
||||
return self::providerForAnyValues()->withAny('stringType', 'boolType')->with('empty');
|
||||
}
|
||||
|
||||
/** @return array<array{mixed}> */
|
||||
public static function providerForNonScalarValues(): array
|
||||
public static function providerForNonScalarValues(): DataProvider
|
||||
{
|
||||
return self::providerForNonEmptyIterableValues() + self::providerForNonEmptyIterableValues() + [
|
||||
'closure' => [static fn() => 'foo'],
|
||||
'stdClass' => [new stdClass()],
|
||||
'null' => [null],
|
||||
'resource' => [tmpfile()],
|
||||
];
|
||||
return self::providerForAnyValues()->without('stringType', 'boolType', 'intType', 'floatType');
|
||||
}
|
||||
|
||||
/** @return array<array{mixed}> */
|
||||
public static function providerForNonIterableTypes(): array
|
||||
public static function providerForNonIterableTypes(): DataProvider
|
||||
{
|
||||
return array_merge(
|
||||
self::providerForScalarValues(),
|
||||
[
|
||||
'closure' => [static fn() => 'foo'],
|
||||
'stdClass' => [new stdClass()],
|
||||
'null' => [null],
|
||||
'resource' => [tmpfile()],
|
||||
]
|
||||
);
|
||||
return self::providerForAnyValues()->without('iterableType');
|
||||
}
|
||||
|
||||
/** @return array<array{iterable<mixed>}> */
|
||||
public static function providerForIterableTypes(): array
|
||||
public static function providerForIterableTypes(): DataProvider
|
||||
{
|
||||
return array_merge(
|
||||
self::providerForNonEmptyIterableValues(),
|
||||
self::providerForEmptyIterableValues(),
|
||||
);
|
||||
return self::providerForAnyValues()->with('iterableType');
|
||||
}
|
||||
|
||||
/** @return array<array{iterable<mixed>}> */
|
||||
public static function providerForNonEmptyIterableValues(): array
|
||||
public static function providerForNonEmptyIterableValues(): DataProvider
|
||||
{
|
||||
return [
|
||||
'ArrayObject' => [new ArrayObject([1, 2, 3])],
|
||||
'array' => [[4, 5, 6]],
|
||||
'generator' => [(static fn() => yield 7)()], // phpcs:ignore
|
||||
];
|
||||
return self::providerForAnyValues()->with('iterableType')->without('empty');
|
||||
}
|
||||
|
||||
/** @return array<array{iterable<mixed>}> */
|
||||
public static function providerForEmptyIterableValues(): array
|
||||
public static function providerForEmptyIterableValues(): DataProvider
|
||||
{
|
||||
return [
|
||||
'empty ArrayObject' => [new ArrayObject([])],
|
||||
'empty array' => [[]],
|
||||
'empty generator' => [(static fn() => yield from [])()],
|
||||
];
|
||||
return self::providerForAnyValues()->with('iterableType', 'empty');
|
||||
}
|
||||
|
||||
/** @return array<array{string}> */
|
||||
public static function providerForStringTypes(): array
|
||||
public static function providerForStringTypes(): DataProvider
|
||||
{
|
||||
return [
|
||||
'string' => ['string'],
|
||||
'empty string' => [''],
|
||||
'integer string' => ['500'],
|
||||
'float string' => ['56.8'],
|
||||
'zero string' => ['0'],
|
||||
];
|
||||
return self::providerForAnyValues()->with('stringType');
|
||||
}
|
||||
|
||||
/** @return array<array{string}> */
|
||||
public static function providerForNonEmptyStringTypes(): array
|
||||
public static function providerForCountable(): DataProvider
|
||||
{
|
||||
$dataProvider = self::providerForStringTypes();
|
||||
unset($dataProvider['empty string']);
|
||||
|
||||
return $dataProvider;
|
||||
return self::providerForAnyValues()->with('countable');
|
||||
}
|
||||
|
||||
/** @return array<array{mixed}> */
|
||||
public static function providerForNonStringTypes(): array
|
||||
public static function providerForNonEmptyStringTypes(): DataProvider
|
||||
{
|
||||
return array_merge(
|
||||
self::providerForNonScalarValues(),
|
||||
self::providerForIntegerTypes(),
|
||||
self::providerForBooleanTypes(),
|
||||
self::providerForFloatTypes(),
|
||||
);
|
||||
return self::providerForAnyValues()->with('stringType')->without('empty');
|
||||
}
|
||||
|
||||
/** @return array<array{int}> */
|
||||
public static function providerForIntegerTypes(): array
|
||||
public static function providerForNonStringTypes(): DataProvider
|
||||
{
|
||||
return [
|
||||
'zero integer' => [0],
|
||||
'positive integer' => [PHP_INT_MAX],
|
||||
'negative integer' => [PHP_INT_MIN],
|
||||
];
|
||||
return self::providerForAnyValues()->without('stringType');
|
||||
}
|
||||
|
||||
/** @return array<array{bool}> */
|
||||
public static function providerForBooleanTypes(): array
|
||||
public static function providerForIntegerTypes(): DataProvider
|
||||
{
|
||||
return [
|
||||
'true' => [true],
|
||||
'false' => [false],
|
||||
];
|
||||
return self::providerForAnyValues()->with('intType');
|
||||
}
|
||||
|
||||
/** @return array<array{float}> */
|
||||
public static function providerForFloatTypes(): array
|
||||
public static function providerForBooleanTypes(): DataProvider
|
||||
{
|
||||
return [
|
||||
'zero float' => [0.0],
|
||||
'negative float' => [-893.1],
|
||||
'positive float' => [32.890],
|
||||
];
|
||||
return self::providerForAnyValues()->with('boolType');
|
||||
}
|
||||
|
||||
/** @return array<array{mixed}> */
|
||||
public static function providerForNonArrayTypes(): array
|
||||
public static function providerForFloatTypes(): DataProvider
|
||||
{
|
||||
$scalarValues = self::providerForNonScalarValues();
|
||||
unset($scalarValues['array']);
|
||||
|
||||
return array_merge(
|
||||
self::providerForIntegerTypes(),
|
||||
self::providerForBooleanTypes(),
|
||||
self::providerForFloatTypes(),
|
||||
self::providerForStringTypes(),
|
||||
$scalarValues,
|
||||
);
|
||||
return self::providerForAnyValues()->with('floatType');
|
||||
}
|
||||
|
||||
/** @return array<array{mixed}> */
|
||||
public static function providerForNonArrayValues(): array
|
||||
public static function providerForNonArrayTypes(): DataProvider
|
||||
{
|
||||
$arrayTypes = self::providerForNonArrayTypes();
|
||||
unset($arrayTypes['ArrayObject']);
|
||||
return self::providerForAnyValues()->without('arrayType');
|
||||
}
|
||||
|
||||
return $arrayTypes;
|
||||
public static function providerForNonArrayValues(): DataProvider
|
||||
{
|
||||
return self::providerForAnyValues()->without('arrayType', 'ArrayObject');
|
||||
}
|
||||
|
||||
public static function providerForUndefined(): DataProvider
|
||||
{
|
||||
return self::providerForAnyValues()->with('undefined');
|
||||
}
|
||||
|
||||
public static function providerForNotUndefined(): DataProvider
|
||||
{
|
||||
return self::providerForAnyValues()->without('undefined');
|
||||
}
|
||||
|
||||
public static function providerForResourceType(): DataProvider
|
||||
{
|
||||
return self::providerForAnyValues()->with('resourceType');
|
||||
}
|
||||
|
||||
public static function providerForNonResourceType(): DataProvider
|
||||
{
|
||||
return self::providerForAnyValues()->without('resourceType');
|
||||
}
|
||||
|
||||
/** @return array<string, array{string|int, array<mixed>}> */
|
||||
|
|
|
@ -13,7 +13,6 @@ use PHPUnit\Framework\Attributes\CoversClass;
|
|||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\DataProvider\UndefinedProvider;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
#[Group('helper')]
|
||||
|
@ -21,7 +20,6 @@ use Respect\Validation\Test\TestCase;
|
|||
final class CanValidateUndefinedTest extends TestCase
|
||||
{
|
||||
use CanValidateUndefined;
|
||||
use UndefinedProvider;
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForUndefined')]
|
||||
|
|
|
@ -10,44 +10,26 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Rules;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
use stdClass;
|
||||
|
||||
use function stream_context_create;
|
||||
use function tmpfile;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
#[Group('rule')]
|
||||
#[CoversClass(ResourceType::class)]
|
||||
final class ResourceTypeTest extends RuleTestCase
|
||||
final class ResourceTypeTest extends TestCase
|
||||
{
|
||||
/** @return iterable<array{ResourceType, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
#[Test]
|
||||
#[DataProvider('providerForResourceType')]
|
||||
public function shouldValidateValidInput(mixed $input): void
|
||||
{
|
||||
$rule = new ResourceType();
|
||||
|
||||
return [
|
||||
[$rule, stream_context_create()],
|
||||
[$rule, tmpfile()],
|
||||
];
|
||||
self::assertValidInput(new ResourceType(), $input);
|
||||
}
|
||||
|
||||
/** @return iterable<array{ResourceType, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
#[Test]
|
||||
#[DataProvider('providerForNonResourceType')]
|
||||
public function shouldValidateInvalidInput(mixed $input): void
|
||||
{
|
||||
$rule = new ResourceType();
|
||||
|
||||
return [
|
||||
[$rule, 'String'],
|
||||
[$rule, 123],
|
||||
[$rule, []],
|
||||
[
|
||||
$rule,
|
||||
static function (): void {
|
||||
},
|
||||
],
|
||||
[$rule, new stdClass()],
|
||||
[$rule, null],
|
||||
];
|
||||
self::assertInvalidInput(new ResourceType(), $input);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue