From 318912302c43c54d6bc64f9f44b94043bc90a5c1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 25 Feb 2024 01:11:41 +0100 Subject: [PATCH] PHPUnit 11 | AssertArrayWithListKeys trait: polyfill the Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys() et al methods PHPUnit 11.0.0 introduces the new `Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys()`, `Assert::assertArrayIsEqualToArrayIgnoringListOfKeys()`, `Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys()` and `Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys()` methods. This commit: * Adds two traits with the same name. One to polyfill the methods when not available in PHPUnit. The other - an empty trait - to allow for `use`-ing the trait in PHPUnit versions in which the methods are already natively available. * Logic to the custom autoloader which will load the correct trait depending on the PHPUnit version used. * An availability test and limited functional tests for the functionality polyfilled. Includes: * Adding the new polyfill to the existing `TestCases` classes. Refs: * sebastianbergmann/phpunit 5600 * sebastianbergmann/phpunit 5716 * sebastianbergmann/phpunit 5729 Co-authored-by: Sebastian Bergmann --- README.md | 19 + phpunitpolyfills-autoload.php | 21 + src/Polyfills/AssertArrayWithListKeys.php | 120 +++++ .../AssertArrayWithListKeys_Empty.php | 8 + src/TestCases/TestCasePHPUnitGte8.php | 2 + src/TestCases/TestCasePHPUnitLte7.php | 2 + src/TestCases/XTestCase.php | 2 + .../Polyfills/AssertArrayWithListKeysTest.php | 489 ++++++++++++++++++ tests/TestCases/TestCaseTestTrait.php | 18 + 9 files changed, 681 insertions(+) create mode 100644 src/Polyfills/AssertArrayWithListKeys.php create mode 100644 src/Polyfills/AssertArrayWithListKeys_Empty.php create mode 100644 tests/Polyfills/AssertArrayWithListKeysTest.php diff --git a/README.md b/README.md index 47a5098..c7d9450 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,25 @@ These methods were later backported to the PHPUnit 9 branch and included in the [`Assert::assertObjectHasProperty()`]: https://docs.phpunit.de/en/main/assertions.html#assertObjectHasProperty [`Assert::assertObjectNotHasProperty()`]: https://docs.phpunit.de/en/main/assertions.html#assertObjectHasProperty +#### PHPUnit < 11.0.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys` + +Polyfills the following method: +| | | +| -------------------------------------------------------------------- | ------------------------------------------------------------- | +| [`Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys()`] | [`Assert::assertArrayIsEqualToArrayIgnoringListOfKeys()`] | +| [`Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys()`] | [`Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys()`] | + +These methods were introduced in PHPUnit 11.0.0. + +This functionality resembles the functionality previously offered by the `Assert::assertArraySubset()` assertion, which was removed in PHPUnit 9.0.0, but with higher precision. + +Refactoring tests which still use `Assert::assertArraySubset()` to use the new assertions should be considered as an upgrade path. + +[`Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys()`]: https://docs.phpunit.de/en/main/assertions.html#assertarrayisequaltoarrayonlyconsideringlistofkeys +[`Assert::assertArrayIsEqualToArrayIgnoringListOfKeys()`]: https://docs.phpunit.de/en/main/assertions.html#assertarrayisequaltoarrayignoringlistofkeys +[`Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys()`]: https://docs.phpunit.de/en/main/assertions.html#assertarrayisidenticaltoarrayonlyconsideringlistofkeys +[`Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys()`]: https://docs.phpunit.de/en/main/assertions.html#assertarrayisidenticaltoarrayignoringlistofkeys + ### TestCases diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 7b6aac6..20caee7 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -82,6 +82,10 @@ public static function load( $className ) { self::loadAssertObjectProperty(); return true; + case 'Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys': + self::loadAssertArrayWithListKeys(); + return true; + case 'Yoast\PHPUnitPolyfills\TestCases\TestCase': self::loadTestCase(); return true; @@ -313,6 +317,23 @@ public static function loadAssertObjectProperty() { require_once __DIR__ . '/src/Polyfills/AssertObjectProperty_Empty.php'; } + /** + * Load the AssertArrayWithListKeys polyfill or an empty trait with the same name + * if a PHPUnit version is used which already contains this functionality. + * + * @return void + */ + public static function loadAssertArrayWithListKeys() { + if ( \method_exists( Assert::class, 'assertArrayIsEqualToArrayOnlyConsideringListOfKeys' ) === false ) { + // PHPUnit < 11.0.0. + require_once __DIR__ . '/src/Polyfills/AssertArrayWithListKeys.php'; + return; + } + + // PHPUnit >= 11.0.0. + require_once __DIR__ . '/src/Polyfills/AssertArrayWithListKeys_Empty.php'; + } + /** * Load the appropriate TestCase class based on the PHPUnit version being used. * diff --git a/src/Polyfills/AssertArrayWithListKeys.php b/src/Polyfills/AssertArrayWithListKeys.php new file mode 100644 index 0000000..26c0a11 --- /dev/null +++ b/src/Polyfills/AssertArrayWithListKeys.php @@ -0,0 +1,120 @@ + $expected Expected value. + * @param array $actual The variable to test. + * @param array $keysToBeConsidered The array keys to take into account. + * @param string $message Optional failure message to display. + * + * @return void + */ + final public static function assertArrayIsEqualToArrayOnlyConsideringListOfKeys( array $expected, array $actual, array $keysToBeConsidered, string $message = '' ) { + $filteredExpected = []; + foreach ( $keysToBeConsidered as $key ) { + if ( isset( $expected[ $key ] ) ) { + $filteredExpected[ $key ] = $expected[ $key ]; + } + } + + $filteredActual = []; + foreach ( $keysToBeConsidered as $key ) { + if ( isset( $actual[ $key ] ) ) { + $filteredActual[ $key ] = $actual[ $key ]; + } + } + + static::assertEquals( $filteredExpected, $filteredActual, $message ); + } + + /** + * Asserts that two arrays are equal while ignoring array elements for which the keys have been specified. + * + * {@internal As the array type declarations don't lead to type juggling, even without strict_types, + * it is safe to let PHP handle the parameter validation.} + * + * @param array $expected Expected value. + * @param array $actual The variable to test. + * @param array $keysToBeIgnored The array keys to ignore. + * @param string $message Optional failure message to display. + * + * @return void + */ + final public static function assertArrayIsEqualToArrayIgnoringListOfKeys( array $expected, array $actual, array $keysToBeIgnored, string $message = '' ) { + foreach ( $keysToBeIgnored as $key ) { + unset( $expected[ $key ], $actual[ $key ] ); + } + + static::assertEquals( $expected, $actual, $message ); + } + + /** + * Asserts that two arrays are identical while only considering array elements for which the keys have been specified. + * + * {@internal As the array type declarations don't lead to type juggling, even without strict_types, + * it is safe to let PHP handle the parameter validation.} + * + * @param array $expected Expected value. + * @param array $actual The variable to test. + * @param array $keysToBeConsidered The array keys to take into account. + * @param string $message Optional failure message to display. + * + * @return void + */ + final public static function assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( array $expected, array $actual, array $keysToBeConsidered, string $message = '' ) { + $keysToBeConsidered = \array_combine( $keysToBeConsidered, $keysToBeConsidered ); + $expected = \array_intersect_key( $expected, $keysToBeConsidered ); + $actual = \array_intersect_key( $actual, $keysToBeConsidered ); + + static::assertSame( $expected, $actual, $message ); + } + + /** + * Asserts that two arrays are identical while ignoring array elements for which the keys have been specified. + * + * {@internal As the array type declarations don't lead to type juggling, even without strict_types, + * it is safe to let PHP handle the parameter validation.} + * + * @param array $expected Expected value. + * @param array $actual The variable to test. + * @param array $keysToBeIgnored The array keys to ignore. + * @param string $message Optional failure message to display. + * + * @return void + */ + final public static function assertArrayIsIdenticalToArrayIgnoringListOfKeys( array $expected, array $actual, array $keysToBeIgnored, string $message = '' ) { + foreach ( $keysToBeIgnored as $key ) { + unset( $expected[ $key ], $actual[ $key ] ); + } + + static::assertSame( $expected, $actual, $message ); + } +} diff --git a/src/Polyfills/AssertArrayWithListKeys_Empty.php b/src/Polyfills/AssertArrayWithListKeys_Empty.php new file mode 100644 index 0000000..0e647c6 --- /dev/null +++ b/src/Polyfills/AssertArrayWithListKeys_Empty.php @@ -0,0 +1,8 @@ += 11.0.0 in which the polyfill is not needed. + */ +trait AssertArrayWithListKeys {} diff --git a/src/TestCases/TestCasePHPUnitGte8.php b/src/TestCases/TestCasePHPUnitGte8.php index 2bc12e8..858d9eb 100644 --- a/src/TestCases/TestCasePHPUnitGte8.php +++ b/src/TestCases/TestCasePHPUnitGte8.php @@ -3,6 +3,7 @@ namespace Yoast\PHPUnitPolyfills\TestCases; use PHPUnit\Framework\TestCase as PHPUnit_TestCase; +use Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys; use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertIgnoringLineEndings; @@ -24,6 +25,7 @@ */ abstract class TestCase extends PHPUnit_TestCase { + use AssertArrayWithListKeys; use AssertClosedResource; use AssertFileEqualsSpecializations; use AssertIgnoringLineEndings; diff --git a/src/TestCases/TestCasePHPUnitLte7.php b/src/TestCases/TestCasePHPUnitLte7.php index a662511..8dcd2de 100644 --- a/src/TestCases/TestCasePHPUnitLte7.php +++ b/src/TestCases/TestCasePHPUnitLte7.php @@ -3,6 +3,7 @@ namespace Yoast\PHPUnitPolyfills\TestCases; use PHPUnit\Framework\TestCase as PHPUnit_TestCase; +use Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys; use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; @@ -27,6 +28,7 @@ */ abstract class TestCase extends PHPUnit_TestCase { + use AssertArrayWithListKeys; use AssertClosedResource; use AssertEqualsSpecializations; use AssertFileEqualsSpecializations; diff --git a/src/TestCases/XTestCase.php b/src/TestCases/XTestCase.php index ff90f6a..af2bf1d 100644 --- a/src/TestCases/XTestCase.php +++ b/src/TestCases/XTestCase.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\Attributes\Before; use PHPUnit\Framework\Attributes\BeforeClass; use PHPUnit\Framework\TestCase as PHPUnit_TestCase; +use Yoast\PHPUnitPolyfills\Polyfills\AssertArrayWithListKeys; use Yoast\PHPUnitPolyfills\Polyfills\AssertClosedResource; use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\AssertFileEqualsSpecializations; @@ -33,6 +34,7 @@ */ abstract class XTestCase extends PHPUnit_TestCase { + use AssertArrayWithListKeys; use AssertClosedResource; use AssertEqualsSpecializations; use AssertFileEqualsSpecializations; diff --git a/tests/Polyfills/AssertArrayWithListKeysTest.php b/tests/Polyfills/AssertArrayWithListKeysTest.php new file mode 100644 index 0000000..5172d0f --- /dev/null +++ b/tests/Polyfills/AssertArrayWithListKeysTest.php @@ -0,0 +1,489 @@ +expectException( TypeError::class ); + + if ( \PHP_VERSION_ID >= 80000 ) { + $msg = '::' . $method . '(): Argument #1 ($expected) must be of type array, '; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 7. + $pattern = '`^Argument 1 passed to [^\s]*::' . $method . '\(\) must be of the type array, `'; + $this->expectExceptionMessageMatches( $pattern ); + } + + $this->$method( $input, [], [] ); + } + + /** + * Verify that the methods throw an error when the $actual parameter is not an array. + * + * @dataProvider dataAllMethodsAllNonArrayTypes + * + * @param string $method Name of the assertion method to test. + * @param mixed $input Non-array value. + * + * @return void + */ + #[DataProvider( 'dataAllMethodsAllNonArrayTypes' )] + public function testAssertionFailsOnInvalidInputTypeForActual( $method, $input ) { + $this->expectException( TypeError::class ); + + if ( \PHP_VERSION_ID >= 80000 ) { + $msg = '::' . $method . '(): Argument #2 ($actual) must be of type array, '; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 7. + $pattern = '`^Argument 2 passed to [^\s]*::' . $method . '\(\) must be of the type array, `'; + $this->expectExceptionMessageMatches( $pattern ); + } + + static::$method( [], $input, [] ); + } + + /** + * Verify that the methods throw an error when the $keysToBeConsidered/$keysToBeIgnored parameter is not an array. + * + * @dataProvider dataAllMethodsAllNonArrayTypes + * + * @param string $method Name of the assertion method to test. + * @param mixed $input Non-array value. + * + * @return void + */ + #[DataProvider( 'dataAllMethodsAllNonArrayTypes' )] + public function testAssertionFailsOnInvalidInputTypeForKeys( $method, $input ) { + $this->expectException( TypeError::class ); + + if ( \PHP_VERSION_ID >= 80000 ) { + $pattern = '`::' . $method . '\(\): Argument #3 \(\$keysToBe(Considered|Ignored)\) must be of type array, `'; + } + else { + // PHP 7. + $pattern = '`^Argument 3 passed to [^\s]*::' . $method . '\(\) must be of the type array, `'; + } + + $this->expectExceptionMessageMatches( $pattern ); + + self::$method( [], [], $input ); + } + + /** + * Data provider. + * + * @return array> + */ + public static function dataAllMethodsAllNonArrayTypes() { + // Only testing closed resource to not leak an open resource. + $resource = \fopen( __DIR__ . '/Fixtures/test.txt', 'r' ); + \fclose( $resource ); + + $types = [ + 'null' => null, + 'boolean' => true, + 'integer' => 10, + 'float' => 5.34, + 'string' => 'text', + 'object' => new stdClass(), + 'closed resource' => $resource, + ]; + + $data = []; + $methods = self::dataAllMethods(); + foreach ( $methods as $key => $unused ) { + foreach ( $types as $name => $value ) { + $data[ $key . ' with ' . $name ] = [ + $key, + $value, + ]; + } + } + + return $data; + } + + /** + * Basic availability/functionality test for the assertArrayIsEqualToArrayOnlyConsideringListOfKeys() method. + * + * @return void + */ + public function testAssertArrayIsEqualToArrayOnlyConsideringListOfKeys() { + $expected = [ 'a' => 'b', 'b' => 'c', 0 => 1, 1 => 2 ]; + $actual = [ 'a' => 'b', 'b' => 'b', 0 => 1, 1 => 3 ]; + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'a', 0 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'b' ] ); + } + + /** + * Basic availability/functionality test for the assertArrayIsEqualToArrayIgnoringListOfKeys() method. + * + * @return void + */ + public function testAssertArrayIsEqualToArrayIgnoringListOfKeys() { + $expected = [ 'a' => 'b', 'b' => 'c', 0 => 1, 1 => 2 ]; + $actual = [ 'a' => 'b', 'b' => 'b', 0 => 1, 1 => 3 ]; + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [ 'b', 1 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [ 'b' ] ); + } + + /** + * Basic availability/functionality test for the assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys() method. + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayOnlyConsideringListOfKeys() { + $expected = [ 'a' => 'b', 'b' => 'c', 0 => 1, 1 => 2 ]; + $actual = [ 'a' => 'b', 'b' => 'b', 0 => 1, 1 => 3 ]; + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'a', 0 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'b' ] ); + } + + /** + * Basic availability/functionality test for the assertArrayIsIdenticalToArrayIgnoringListOfKeys() method. + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayIgnoringListOfKeys() { + $expected = [ 'a' => 'b', 'b' => 'c', 0 => 1, 1 => 2 ]; + $actual = [ 'a' => 'b', 'b' => 'b', 0 => 1, 1 => 3 ]; + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [ 'b', 1 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [ 'b' ] ); + } + + /** + * Verify that the assertArrayIsEqualToArrayOnlyConsideringListOfKeys() method handles the keys + * passed in $keysToBeConsidered the same way as PHP handles array keys. + * + * @link https://github.com/sebastianbergmann/phpunit/pull/5716 + * + * @return void + */ + public function testAssertArrayIsEqualToArrayOnlyConsideringListOfKeysInterpretsKeysSameAsPHPBug5716() { + if ( \version_compare( PHPUnit_Version::id(), '11.0.0', '>=' ) + && \version_compare( PHPUnit_Version::id(), '11.0.4', '<' ) + ) { + // This bug was fixed in PHPUnit 11.0.4. + $this->markTestSkipped( 'Skipping test on PHPUnit versions which contained bug #5716' ); + } + + // Effective keys: int 0, int 1, int 2, string '3.0'. + $expected = [ 0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4 ]; + $actual = [ 0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4 ]; + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 0, '1', '3.0' ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [ '1', 2.0, '3.0' ] ); + } + + /** + * Verify that the assertArrayIsEqualToArrayIgnoringListOfKeys() method handles the keys + * passed in $keysToBeIgnored the same way as PHP handles array keys. + * + * @return void + */ + public function testAssertArrayIsEqualToArrayIgnoringListOfKeysInterpretsKeysSameAsPHP() { + // Effective keys: int 0, int 1, int 2, string '3.0'. + $expected = [ 0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4 ]; + $actual = [ 0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4 ]; + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [ 2.0 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [ '1' ] ); + } + + /** + * Verify that the assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys() method handles the keys + * passed in $keysToBeConsidered the same way as PHP handles array keys. + * + * @link https://github.com/sebastianbergmann/phpunit/pull/5716 + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayOnlyConsideringListOfKeysInterpretsKeysSameAsPHPBug5716() { + if ( \version_compare( PHPUnit_Version::id(), '11.0.0', '>=' ) + && \version_compare( PHPUnit_Version::id(), '11.0.4', '<' ) + ) { + // This bug was fixed in PHPUnit 11.0.4. + $this->markTestSkipped( 'Skipping test on PHPUnit versions which contained bug #5716' ); + } + // Effective keys: int 0, int 1, int 2, string '3.0'. + $expected = [ 0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4 ]; + $actual = [ 0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4 ]; + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 0, '1', '3.0' ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ '1', 2.0, '3.0' ] ); + } + + /** + * Verify that the assertArrayIsIdenticalToArrayIgnoringListOfKeys() method handles the keys + * passed in $keysToBeIgnored the same way as PHP handles array keys. + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayIgnoringListOfKeysInterpretsKeysSameAsPHP() { + // Effective keys: int 0, int 1, int 2, string '3.0'. + $expected = [ 0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4 ]; + $actual = [ 0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4 ]; + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [ 2.0 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [ '1' ] ); + } + + /** + * Verify the assertArrayIsEqualToArrayOnlyConsideringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsEqualToArrayOnlyConsideringListOfKeysWhenActualIsEmptyArray() { + $expected = [ 'a' => 'b', 1 => 2 ]; + $actual = []; + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'b', 0 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'a' ] ); + } + + /** + * Verify the assertArrayIsEqualToArrayIgnoringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsEqualToArrayIgnoringListOfKeysWhenActualIsEmptyArray() { + $expected = [ 'a' => 'b', 1 => 2 ]; + $actual = []; + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [ 'a', 1 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [ 'b' ] ); + } + + /** + * Verify the assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayOnlyConsideringListOfKeysWhenExpectedIsEmptyArray() { + $expected = []; + $actual = [ 'a' => 'b', 1 => 2 ]; + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'b', 0 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 1 ] ); + } + + /** + * Verify the assertArrayIsIdenticalToArrayIgnoringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayIgnoringListOfKeysWhenExpectedIsEmptyArray() { + $expected = []; + $actual = [ 'a' => 'b', 1 => 2 ]; + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [ 'a', 1 ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [ '0' ] ); + } + + /** + * Verify the assertArrayIsEqualToArrayOnlyConsideringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsEqualToArrayOnlyConsideringListOfKeysWhenListIsEmptyArray() { + $expected = [ 'a' => 'b', 1 => 2 ]; + $actual = [ 'a' => 'b', 1 => 2 ]; + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [] ); + } + + /** + * Verify the assertArrayIsEqualToArrayIgnoringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsEqualToArrayIgnoringListOfKeysWhenListIsEmptyArray() { + $expected = [ 'a' => 'b', 1 => 2 ]; + $actual = [ 'a' => 'b', 1 => 2 ]; + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [] ); + } + + /** + * Verify the assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayOnlyConsideringListOfKeysWhenListIsEmptyArray() { + $expected = [ 'a' => 'b', 1 => 2 ]; + $actual = [ 'a' => 'b', 1 => 2 ]; + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [] ); + } + + /** + * Verify the assertArrayIsIdenticalToArrayIgnoringListOfKeys() method compares empty arrays as equal. + * + * @return void + */ + public function testAssertArrayIsIdenticalToArrayIgnoringListOfKeysWhenListIsEmptyArray() { + $expected = [ 'a' => 'b', 1 => 2 ]; + $actual = [ 'a' => 'b', 1 => 2 ]; + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [] ); + } + + /** + * Verify handling when arrays are equal, but not identical. + * + * @link https://github.com/sebastianbergmann/phpunit/pull/5729 + * + * @return void + */ + public function testAssertArrayIsEqualButNotIdenticalToArrayOnlyConsideringListOfKeys() { + $expected = [ 'a' => 'b', 'b' => 'c', 0 => 1, 1 => 2 ]; + $actual = [ 0 => 1, 1 => 3, 'a' => 'b', 'b' => 'b' ]; + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'a', 0 ] ); + + if ( \version_compare( PHPUnit_Version::id(), '11.0.4', '>=' ) + && \version_compare( PHPUnit_Version::id(), '11.0.6', '<' ) + ) { + // This bug only exists in PHPUnit 11.0.4 and 11.0.5. + $this->markTestSkipped( 'Skipping test on PHPUnit versions which contained bug #5729' ); + } + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'a', 0 ] ); + } + + /** + * Verify handling when arrays are equal, but not identical. + * + * @return void + */ + public function testAssertArrayIsEqualButNotIdenticalToArrayIgnoringListOfKeys() { + $expected = [ 'a' => 'b', 'b' => 'c', 0 => 1, 1 => 2 ]; + $actual = [ 0 => 1, 1 => 3, 'a' => 'b', 'b' => 'b' ]; + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys( $expected, $actual, [ 'b', '1' ] ); + + $this->expectException( AssertionFailedError::class ); + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys( $expected, $actual, [ 'b', '1' ] ); + } + + /** + * Verify that the methods fail a test with a custom failure message, + * when the custom $message parameter has been passed. + * + * @dataProvider dataAllMethods + * + * @param string $method Name of the assertion method to test. + * + * @return void + */ + #[DataProvider( 'dataAllMethods' )] + public function testAssertionFailsWithCustomMessage( $method ) { + $pattern = '`^This assertion failed for reason XYZ\s+(?:Failed asserting that two arrays are (?:equal|identical)\.|Failed asserting that Array &0)`'; + + $this->expectException( AssertionFailedError::class ); + $this->expectExceptionMessageMatches( $pattern ); + + $this->$method( [ 'key' => 10 ], [ 1, 2, 3 ], [ 'key' ], 'This assertion failed for reason XYZ' ); + } + + /** + * Data provider. + * + * @return array> + */ + public static function dataAllMethods() { + $methods = [ + 'assertArrayIsEqualToArrayOnlyConsideringListOfKeys', + 'assertArrayIsEqualToArrayIgnoringListOfKeys', + 'assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys', + 'assertArrayIsIdenticalToArrayIgnoringListOfKeys', + ]; + + $data = []; + foreach ( $methods as $method ) { + $data[ $method ] = [ $method ]; + } + + return $data; + } +} diff --git a/tests/TestCases/TestCaseTestTrait.php b/tests/TestCases/TestCaseTestTrait.php index 9bdacf6..3f51cc5 100644 --- a/tests/TestCases/TestCaseTestTrait.php +++ b/tests/TestCases/TestCaseTestTrait.php @@ -148,4 +148,22 @@ final public function testAvailabilityAssertObjectProperty() { self::assertObjectHasProperty( 'prop', $object ); } + + /** + * Verify availability of trait polyfilled PHPUnit methods [18]. + * + * @return void + */ + final public function testAvailabilityAssertArrayWithListKeys() { + $expected = [ + 'a' => 'b', + 'b' => 'c', + ]; + $actual = [ + 'a' => 'b', + 'b' => 'b', + ]; + + self::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'a' ] ); + } }