Skip to content

Commit 10614ef

Browse files
committed
Import current implementation as-is
Copy over from PHPunit the code that will be deprecated, adjusting only the needed tests, some coding style fixes and the namespaces. More refactoring and revisiting to follow.
1 parent d67951d commit 10614ef

File tree

4 files changed

+315
-0
lines changed

4 files changed

+315
-0
lines changed

src/Assert.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DMS\PHPUnitExtensions\ArraySubset;
5+
6+
use ArrayAccess;
7+
use DMS\PHPUnitExtensions\ArraySubset\Constraint\ArraySubset;
8+
use Exception;
9+
use PHPUnit\Framework\Assert as PhpUnitAssert;
10+
use PHPUnit\Framework\ExpectationFailedException;
11+
use PHPUnit\Util\InvalidArgumentHelper;
12+
use SebastianBergmann\RecursionContext\InvalidArgumentException;
13+
use function is_array;
14+
15+
final class Assert
16+
{
17+
/**
18+
* Asserts that an array has a specified subset.
19+
*
20+
* @param array|ArrayAccess $subset
21+
* @param array|ArrayAccess $array
22+
*
23+
* @throws ExpectationFailedException
24+
* @throws InvalidArgumentException
25+
* @throws Exception
26+
*/
27+
public static function assertArraySubset($subset, $array, bool $checkForObjectIdentity = false, string $message = ''): void
28+
{
29+
if (! (is_array($subset) || $subset instanceof ArrayAccess)) {
30+
throw InvalidArgumentHelper::factory(
31+
1,
32+
'array or ArrayAccess'
33+
);
34+
}
35+
if (! (is_array($array) || $array instanceof ArrayAccess)) {
36+
throw InvalidArgumentHelper::factory(
37+
2,
38+
'array or ArrayAccess'
39+
);
40+
}
41+
$constraint = new ArraySubset($subset, $checkForObjectIdentity);
42+
PhpUnitAssert::assertThat($array, $constraint, $message);
43+
}
44+
}

src/Constraint/ArraySubset.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DMS\PHPUnitExtensions\ArraySubset\Constraint;
5+
6+
use ArrayObject;
7+
use PHPUnit\Framework\Constraint\Constraint;
8+
use PHPUnit\Framework\ExpectationFailedException;
9+
use SebastianBergmann\Comparator\ComparisonFailure;
10+
use SebastianBergmann\RecursionContext\InvalidArgumentException;
11+
use Traversable;
12+
use function array_replace_recursive;
13+
use function is_array;
14+
use function iterator_to_array;
15+
use function var_export;
16+
17+
/**
18+
* Constraint that asserts that the array it is evaluated for has a specified subset.
19+
*
20+
* Uses array_replace_recursive() to check if a key value subset is part of the
21+
* subject array.
22+
*/
23+
final class ArraySubset extends Constraint
24+
{
25+
/**
26+
* @var iterable
27+
*/
28+
private $subset;
29+
/**
30+
* @var bool
31+
*/
32+
private $strict;
33+
34+
public function __construct(iterable $subset, bool $strict = false)
35+
{
36+
$this->strict = $strict;
37+
$this->subset = $subset;
38+
}
39+
40+
/**
41+
* Evaluates the constraint for parameter $other
42+
*
43+
* If $returnResult is set to false (the default), an exception is thrown
44+
* in case of a failure. null is returned otherwise.
45+
*
46+
* If $returnResult is true, the result of the evaluation is returned as
47+
* a boolean value instead: true in case of success, false in case of a
48+
* failure.
49+
*
50+
* @throws ExpectationFailedException
51+
* @throws InvalidArgumentException
52+
*/
53+
public function evaluate($other, string $description = '', bool $returnResult = false)
54+
{
55+
//type cast $other & $this->subset as an array to allow
56+
//support in standard array functions.
57+
$other = $this->toArray($other);
58+
$this->subset = $this->toArray($this->subset);
59+
$patched = array_replace_recursive($other, $this->subset);
60+
if ($this->strict) {
61+
$result = $other === $patched;
62+
} else {
63+
$result = $other == $patched;
64+
}
65+
if ($returnResult) {
66+
return $result;
67+
}
68+
if ($result) {
69+
return;
70+
}
71+
72+
$f = new ComparisonFailure(
73+
$patched,
74+
$other,
75+
var_export($patched, true),
76+
var_export($other, true)
77+
);
78+
$this->fail($other, $description, $f);
79+
}
80+
81+
/**
82+
* Returns a string representation of the constraint.
83+
*
84+
* @throws InvalidArgumentException
85+
*/
86+
public function toString(): string
87+
{
88+
return 'has the subset ' . $this->exporter()->export($this->subset);
89+
}
90+
91+
/**
92+
* Returns the description of the failure
93+
*
94+
* The beginning of failure messages is "Failed asserting that" in most
95+
* cases. This method should return the second part of that sentence.
96+
*
97+
* @param mixed $other evaluated value or object
98+
*
99+
* @throws InvalidArgumentException
100+
*/
101+
protected function failureDescription($other): string
102+
{
103+
return 'an array ' . $this->toString();
104+
}
105+
106+
private function toArray(iterable $other): array
107+
{
108+
if (is_array($other)) {
109+
return $other;
110+
}
111+
if ($other instanceof ArrayObject) {
112+
return $other->getArrayCopy();
113+
}
114+
if ($other instanceof Traversable) {
115+
return iterator_to_array($other);
116+
}
117+
// Keep BC even if we know that array would not be the expected one
118+
return (array) $other;
119+
}
120+
}

tests/Unit/AssertTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DMS\PHPUnitExtensions\ArraySubset\Tests\Unit;
5+
6+
use DMS\PHPUnitExtensions\ArraySubset\Assert;
7+
use PHPUnit\Framework\ExpectationFailedException;
8+
use PHPUnit\Framework\TestCase;
9+
use PHPUnit\Framework\Exception;
10+
11+
final class AssertTest extends TestCase
12+
{
13+
public function testAssertArraySubsetPassesStrictConfig(): void
14+
{
15+
$this->expectException(ExpectationFailedException::class);
16+
Assert::assertArraySubset(['bar' => 0], ['bar' => '0'], true);
17+
}
18+
19+
public function testAssertArraySubsetThrowsExceptionForInvalidSubset(): void
20+
{
21+
$this->expectException(ExpectationFailedException::class);
22+
Assert::assertArraySubset([6,7], [1,2,3,4,5,6]);
23+
}
24+
25+
public function testAssertArraySubsetThrowsExceptionForInvalidSubsetArgument(): void
26+
{
27+
$this->expectException(Exception::class);
28+
Assert::assertArraySubset('string', '');
29+
}
30+
31+
public function testAssertArraySubsetThrowsExceptionForInvalidArrayArgument(): void
32+
{
33+
$this->expectException(Exception::class);
34+
Assert::assertArraySubset([], '');
35+
}
36+
37+
public function testAssertArraySubsetDoesNothingForValidScenario(): void
38+
{
39+
Assert::assertArraySubset([1,2], [1,2,3]);
40+
}
41+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
5+
namespace DMS\PHPUnitExtensions\ArraySubset\Tests\Unit\Constraint;
6+
7+
use ArrayAccessible;
8+
use ArrayObject;
9+
use Countable;
10+
use DMS\PHPUnitExtensions\ArraySubset\Constraint\ArraySubset;
11+
use PHPUnit\Framework\ExpectationFailedException;
12+
use PHPUnit\Framework\SelfDescribing;
13+
use PHPUnit\Framework\TestCase;
14+
use ReflectionClass;
15+
use SebastianBergmann\RecursionContext\InvalidArgumentException;
16+
use Traversable;
17+
use function sprintf;
18+
19+
final class ArraySubsetTest extends TestCase
20+
{
21+
public static function evaluateDataProvider(): array
22+
{
23+
return [
24+
'loose array subset and array other' => [
25+
'expected' => true,
26+
'subset' => ['bar' => 0],
27+
'other' => ['foo' => '', 'bar' => '0'],
28+
'strict' => false,
29+
],
30+
'strict array subset and array other' => [
31+
'expected' => false,
32+
'subset' => ['bar' => 0],
33+
'other' => ['foo' => '', 'bar' => '0'],
34+
'strict' => true,
35+
],
36+
'loose array subset and ArrayObject other' => [
37+
'expected' => true,
38+
'subset' => ['bar' => 0],
39+
'other' => new ArrayObject(['foo' => '', 'bar' => '0']),
40+
'strict' => false,
41+
],
42+
'strict ArrayObject subset and array other' => [
43+
'expected' => true,
44+
'subset' => new ArrayObject(['bar' => 0]),
45+
'other' => ['foo' => '', 'bar' => 0],
46+
'strict' => true,
47+
],
48+
];
49+
}
50+
51+
/**
52+
* @param array|Traversable $subset
53+
* @param array|Traversable $other
54+
* @param bool $strict
55+
*
56+
* @throws ExpectationFailedException
57+
* @throws InvalidArgumentException
58+
* @dataProvider evaluateDataProvider
59+
*/
60+
public function testEvaluate(bool $expected, $subset, $other, $strict): void
61+
{
62+
$constraint = new ArraySubset($subset, $strict);
63+
64+
$this->assertSame($expected, $constraint->evaluate($other, '', true));
65+
}
66+
67+
public function testEvaluateWithArrayAccess(): void
68+
{
69+
$arrayAccess = new ArrayAccessible(['foo' => 'bar']);
70+
71+
$constraint = new ArraySubset(['foo' => 'bar']);
72+
73+
$this->assertTrue($constraint->evaluate($arrayAccess, '', true));
74+
}
75+
76+
public function testEvaluateFailMessage(): void
77+
{
78+
$constraint = new ArraySubset(['foo' => 'bar']);
79+
80+
try {
81+
$constraint->evaluate(['baz' => 'bar'], '', false);
82+
$this->fail(sprintf('Expected %s to be thrown.', ExpectationFailedException::class));
83+
} catch (ExpectationFailedException $expectedException) {
84+
$comparisonFailure = $expectedException->getComparisonFailure();
85+
$this->assertNotNull($comparisonFailure);
86+
$this->assertStringContainsString("'foo' => 'bar'", $comparisonFailure->getExpectedAsString());
87+
$this->assertStringContainsString("'baz' => 'bar'", $comparisonFailure->getActualAsString());
88+
}
89+
}
90+
91+
public function testIsCountable(): void
92+
{
93+
$reflection = new ReflectionClass(ArraySubset::class);
94+
95+
$this->assertTrue($reflection->implementsInterface(Countable::class), sprintf(
96+
'Failed to assert that ArraySubset implements "%s".',
97+
Countable::class
98+
));
99+
}
100+
101+
public function testIsSelfDescribing(): void
102+
{
103+
$reflection = new ReflectionClass(ArraySubset::class);
104+
105+
$this->assertTrue($reflection->implementsInterface(SelfDescribing::class), sprintf(
106+
'Failed to assert that Array implements "%s".',
107+
SelfDescribing::class
108+
));
109+
}
110+
}

0 commit comments

Comments
 (0)