diff --git a/README.md b/README.md index c7d9450..c0c3208 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,17 @@ Refactoring tests which still use `Assert::assertArraySubset()` to use the new a [`Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys()`]: https://docs.phpunit.de/en/main/assertions.html#assertarrayisidenticaltoarrayonlyconsideringlistofkeys [`Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys()`]: https://docs.phpunit.de/en/main/assertions.html#assertarrayisidenticaltoarrayignoringlistofkeys +#### PHPUnit < 11.2.0: `Yoast\PHPUnitPolyfills\Polyfills\AssertObjectNotEquals` + +Polyfills the [`Assert::assertObjectNotEquals()`] method to verify two (value) objects are **_not_** considered equal. +This is the sister-method to the PHPUnit 9.4+ `Assert::assertObjectEquals()` method. + +This assertion expects an object to contain a comparator method in the object itself. This comparator method is subsequently called to verify the "equalness" of the objects. + +The `assertObjectNotEquals()` assertion was introduced in PHPUnit 11.2.0. + +[`Assert::assertObjectNotEquals()`]: https://docs.phpunit.de/en/main/assertions.html#assertobjectequals + ### TestCases diff --git a/phpunitpolyfills-autoload.php b/phpunitpolyfills-autoload.php index 20caee7..3d57ccb 100644 --- a/phpunitpolyfills-autoload.php +++ b/phpunitpolyfills-autoload.php @@ -86,6 +86,10 @@ public static function load( $className ) { self::loadAssertArrayWithListKeys(); return true; + case 'Yoast\PHPUnitPolyfills\Polyfills\AssertObjectNotEquals': + self::loadAssertObjectNotEquals(); + return true; + case 'Yoast\PHPUnitPolyfills\TestCases\TestCase': self::loadTestCase(); return true; @@ -97,6 +101,7 @@ public static function load( $className ) { /* * Handles: * - Yoast\PHPUnitPolyfills\Exceptions\InvalidComparisonMethodException + * - Yoast\PHPUnitPolyfills\Helpers\ComparatorValidator * - Yoast\PHPUnitPolyfills\Helpers\ResourceHelper * - Yoast\PHPUnitPolyfills\TestCases\XTestCase * - Yoast\PHPUnitPolyfills\TestListeners\TestListenerSnakeCaseMethods @@ -334,6 +339,23 @@ public static function loadAssertArrayWithListKeys() { require_once __DIR__ . '/src/Polyfills/AssertArrayWithListKeys_Empty.php'; } + /** + * Load the AssertObjectNotEquals 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 loadAssertObjectNotEquals() { + if ( \method_exists( Assert::class, 'assertObjectNotEquals' ) === false ) { + // PHPUnit < 11.2.0. + require_once __DIR__ . '/src/Polyfills/AssertObjectNotEquals.php'; + return; + } + + // PHPUnit >= 11.2.0. + require_once __DIR__ . '/src/Polyfills/AssertObjectNotEquals_Empty.php'; + } + /** * Load the appropriate TestCase class based on the PHPUnit version being used. * diff --git a/src/Exceptions/InvalidComparisonMethodException.php b/src/Exceptions/InvalidComparisonMethodException.php index db05e34..a3d077a 100644 --- a/src/Exceptions/InvalidComparisonMethodException.php +++ b/src/Exceptions/InvalidComparisonMethodException.php @@ -5,10 +5,10 @@ use Exception; /** - * Exception used for all errors throw by the polyfill for the `assertObjectEquals()` assertion. + * Exception used for all errors throw by the polyfill for the `assertObjectEquals()` and the `assertObjectNotEquals()` assertions. * * PHPUnit natively throws a range of different exceptions. - * The polyfill throws just one exception type with different messages. + * The polyfills throw just one exception type with different messages. */ final class InvalidComparisonMethodException extends Exception { diff --git a/src/Helpers/ComparatorValidator.php b/src/Helpers/ComparatorValidator.php new file mode 100644 index 0000000..c6c3ed0 --- /dev/null +++ b/src/Helpers/ComparatorValidator.php @@ -0,0 +1,181 @@ +hasMethod( $method ) === false ) { + throw new InvalidComparisonMethodException( + \sprintf( + 'Comparison method %s::%s() does not exist.', + \get_class( $actual ), + $method + ) + ); + } + + $reflMethod = $reflObject->getMethod( $method ); + + /* + * Comparator method return type requirements validation. + */ + $returnTypeError = \sprintf( + 'Comparison method %s::%s() does not declare bool return type.', + \get_class( $actual ), + $method + ); + + if ( $reflMethod->hasReturnType() === false ) { + throw new InvalidComparisonMethodException( $returnTypeError ); + } + + $returnType = $reflMethod->getReturnType(); + + if ( \class_exists( 'ReflectionNamedType' ) ) { + // PHP >= 7.1: guard against union/intersection return types. + if ( ( $returnType instanceof ReflectionNamedType ) === false ) { + throw new InvalidComparisonMethodException( $returnTypeError ); + } + } + elseif ( ( $returnType instanceof ReflectionType ) === false ) { + /* + * PHP 7.0. + * Checking for `ReflectionType` will not throw an error on union types, + * but then again union types are not supported on PHP 7.0. + */ + throw new InvalidComparisonMethodException( $returnTypeError ); + } + + if ( $returnType->allowsNull() === true ) { + throw new InvalidComparisonMethodException( $returnTypeError ); + } + + if ( \method_exists( $returnType, 'getName' ) ) { + // PHP >= 7.1. + if ( $returnType->getName() !== 'bool' ) { + throw new InvalidComparisonMethodException( $returnTypeError ); + } + } + elseif ( (string) $returnType !== 'bool' ) { + // PHP 7.0. + throw new InvalidComparisonMethodException( $returnTypeError ); + } + + /* + * Comparator method parameter requirements validation. + */ + if ( $reflMethod->getNumberOfParameters() !== 1 + || $reflMethod->getNumberOfRequiredParameters() !== 1 + ) { + throw new InvalidComparisonMethodException( + \sprintf( + 'Comparison method %s::%s() does not declare exactly one parameter.', + \get_class( $actual ), + $method + ) + ); + } + + $noDeclaredTypeError = \sprintf( + 'Parameter of comparison method %s::%s() does not have a declared type.', + \get_class( $actual ), + $method + ); + + $notAcceptableTypeError = \sprintf( + '%s is not an accepted argument type for comparison method %s::%s().', + \get_class( $expected ), + \get_class( $actual ), + $method + ); + + $reflParameter = $reflMethod->getParameters()[0]; + + $hasType = $reflParameter->hasType(); + if ( $hasType === false ) { + throw new InvalidComparisonMethodException( $noDeclaredTypeError ); + } + + $type = $reflParameter->getType(); + if ( \class_exists( 'ReflectionNamedType' ) ) { + // PHP >= 7.1. + if ( ( $type instanceof ReflectionNamedType ) === false ) { + throw new InvalidComparisonMethodException( $noDeclaredTypeError ); + } + + $typeName = $type->getName(); + } + else { + /* + * PHP 7.0. + * Checking for `ReflectionType` will not throw an error on union types, + * but then again union types are not supported on PHP 7.0. + */ + if ( ( $type instanceof ReflectionType ) === false ) { + throw new InvalidComparisonMethodException( $noDeclaredTypeError ); + } + + $typeName = (string) $type; + } + + /* + * Validate that the $expected object complies with the declared parameter type. + */ + if ( $typeName === 'self' ) { + $typeName = \get_class( $actual ); + } + + if ( ( $expected instanceof $typeName ) === false ) { + throw new InvalidComparisonMethodException( $notAcceptableTypeError ); + } + } +} diff --git a/src/Polyfills/AssertObjectEquals.php b/src/Polyfills/AssertObjectEquals.php index 2e069bc..efd13e7 100644 --- a/src/Polyfills/AssertObjectEquals.php +++ b/src/Polyfills/AssertObjectEquals.php @@ -2,11 +2,9 @@ namespace Yoast\PHPUnitPolyfills\Polyfills; -use ReflectionNamedType; -use ReflectionObject; -use ReflectionType; use TypeError; use Yoast\PHPUnitPolyfills\Exceptions\InvalidComparisonMethodException; +use Yoast\PHPUnitPolyfills\Helpers\ComparatorValidator; /** * Polyfill the Assert::assertObjectEquals() methods. @@ -83,133 +81,16 @@ final public static function assertObjectEquals( $expected, $actual, $method = ' } /* - * Comparator method validation. + * Validate the comparator method requirements. + * + * If the method does not validate, an InvalidComparisonMethodException is thrown, + * which will cause the test to error out. */ - $reflObject = new ReflectionObject( $actual ); - - if ( $reflObject->hasMethod( $method ) === false ) { - throw new InvalidComparisonMethodException( - \sprintf( - 'Comparison method %s::%s() does not exist.', - \get_class( $actual ), - $method - ) - ); - } - - $reflMethod = $reflObject->getMethod( $method ); - - /* - * Comparator method return type requirements validation. - */ - $returnTypeError = \sprintf( - 'Comparison method %s::%s() does not declare bool return type.', - \get_class( $actual ), - $method - ); - - if ( $reflMethod->hasReturnType() === false ) { - throw new InvalidComparisonMethodException( $returnTypeError ); - } - - $returnType = $reflMethod->getReturnType(); - - if ( \class_exists( 'ReflectionNamedType' ) ) { - // PHP >= 7.1: guard against union/intersection return types. - if ( ( $returnType instanceof ReflectionNamedType ) === false ) { - throw new InvalidComparisonMethodException( $returnTypeError ); - } - } - elseif ( ( $returnType instanceof ReflectionType ) === false ) { - /* - * PHP 7.0. - * Checking for `ReflectionType` will not throw an error on union types, - * but then again union types are not supported on PHP 7.0. - */ - throw new InvalidComparisonMethodException( $returnTypeError ); - } - - if ( $returnType->allowsNull() === true ) { - throw new InvalidComparisonMethodException( $returnTypeError ); - } - - if ( \method_exists( $returnType, 'getName' ) ) { - // PHP 7.1+. - if ( $returnType->getName() !== 'bool' ) { - throw new InvalidComparisonMethodException( $returnTypeError ); - } - } - elseif ( (string) $returnType !== 'bool' ) { - // PHP 7.0. - throw new InvalidComparisonMethodException( $returnTypeError ); - } - - /* - * Comparator method parameter requirements validation. - */ - if ( $reflMethod->getNumberOfParameters() !== 1 - || $reflMethod->getNumberOfRequiredParameters() !== 1 - ) { - throw new InvalidComparisonMethodException( - \sprintf( - 'Comparison method %s::%s() does not declare exactly one parameter.', - \get_class( $actual ), - $method - ) - ); - } - - $noDeclaredTypeError = \sprintf( - 'Parameter of comparison method %s::%s() does not have a declared type.', - \get_class( $actual ), - $method - ); - - $notAcceptableTypeError = \sprintf( - '%s is not an accepted argument type for comparison method %s::%s().', - \get_class( $expected ), - \get_class( $actual ), - $method - ); - - $reflParameter = $reflMethod->getParameters()[0]; - - $hasType = $reflParameter->hasType(); - if ( $hasType === false ) { - throw new InvalidComparisonMethodException( $noDeclaredTypeError ); - } - - $type = $reflParameter->getType(); - if ( \class_exists( 'ReflectionNamedType' ) ) { - // PHP >= 7.1. - if ( ( $type instanceof ReflectionNamedType ) === false ) { - throw new InvalidComparisonMethodException( $noDeclaredTypeError ); - } - - $typeName = $type->getName(); - } - else { - /* - * PHP 7.0. - * Checking for `ReflectionType` will not throw an error on union types, - * but then again union types are not supported on PHP 7.0. - */ - if ( ( $type instanceof ReflectionType ) === false ) { - throw new InvalidComparisonMethodException( $noDeclaredTypeError ); - } - - $typeName = (string) $type; - } - - /* - * Validate that the $expected object complies with the declared parameter type. - */ - if ( $typeName === 'self' ) { - $typeName = \get_class( $actual ); - } - - if ( ( $expected instanceof $typeName ) === false ) { - throw new InvalidComparisonMethodException( $notAcceptableTypeError ); + try { + ComparatorValidator::isValid( $expected, $actual, $method ); + } catch ( InvalidComparisonMethodException $e ) { + // Rethrow to ensure a stack trace shows the exception comes from this method, not the helper. + throw $e; } /* diff --git a/src/Polyfills/AssertObjectNotEquals.php b/src/Polyfills/AssertObjectNotEquals.php new file mode 100644 index 0000000..8463cb2 --- /dev/null +++ b/src/Polyfills/AssertObjectNotEquals.php @@ -0,0 +1,111 @@ +{$method}( $expected ); + + $msg = \sprintf( + 'Failed asserting that two objects are not equal. The objects are equal according to %s::%s()', + \get_class( $actual ), + $method + ); + + if ( $message !== '' ) { + $msg = $message . \PHP_EOL . $msg; + } + + static::assertFalse( $result, $msg ); + } +} diff --git a/src/Polyfills/AssertObjectNotEquals_Empty.php b/src/Polyfills/AssertObjectNotEquals_Empty.php new file mode 100644 index 0000000..7d345df --- /dev/null +++ b/src/Polyfills/AssertObjectNotEquals_Empty.php @@ -0,0 +1,8 @@ += 11.2.0 in which this polyfill is not needed. + */ +trait AssertObjectNotEquals {} diff --git a/src/TestCases/TestCasePHPUnitGte8.php b/src/TestCases/TestCasePHPUnitGte8.php index 858d9eb..830b12a 100644 --- a/src/TestCases/TestCasePHPUnitGte8.php +++ b/src/TestCases/TestCasePHPUnitGte8.php @@ -10,6 +10,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; use Yoast\PHPUnitPolyfills\Polyfills\AssertIsList; use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals; +use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectNotEquals; use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectProperty; use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches; @@ -32,6 +33,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertionRenames; use AssertIsList; use AssertObjectEquals; + use AssertObjectNotEquals; use AssertObjectProperty; use EqualToSpecializations; use ExpectExceptionMessageMatches; diff --git a/src/TestCases/TestCasePHPUnitLte7.php b/src/TestCases/TestCasePHPUnitLte7.php index 8dcd2de..b825415 100644 --- a/src/TestCases/TestCasePHPUnitLte7.php +++ b/src/TestCases/TestCasePHPUnitLte7.php @@ -12,6 +12,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertIsList; use Yoast\PHPUnitPolyfills\Polyfills\AssertIsType; use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals; +use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectNotEquals; use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectProperty; use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; @@ -37,6 +38,7 @@ abstract class TestCase extends PHPUnit_TestCase { use AssertIsList; use AssertIsType; use AssertObjectEquals; + use AssertObjectNotEquals; use AssertObjectProperty; use AssertStringContains; use EqualToSpecializations; diff --git a/src/TestCases/XTestCase.php b/src/TestCases/XTestCase.php index af2bf1d..1d6fb51 100644 --- a/src/TestCases/XTestCase.php +++ b/src/TestCases/XTestCase.php @@ -16,6 +16,7 @@ use Yoast\PHPUnitPolyfills\Polyfills\AssertIsList; use Yoast\PHPUnitPolyfills\Polyfills\AssertIsType; use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals; +use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectNotEquals; use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectProperty; use Yoast\PHPUnitPolyfills\Polyfills\AssertStringContains; use Yoast\PHPUnitPolyfills\Polyfills\EqualToSpecializations; @@ -43,6 +44,7 @@ abstract class XTestCase extends PHPUnit_TestCase { use AssertIsList; use AssertIsType; use AssertObjectEquals; + use AssertObjectNotEquals; use AssertObjectProperty; use AssertStringContains; use EqualToSpecializations; diff --git a/tests/Polyfills/AssertObjectEqualsTest.php b/tests/Polyfills/AssertObjectEqualsTest.php index f9bb6a9..b115f9c 100644 --- a/tests/Polyfills/AssertObjectEqualsTest.php +++ b/tests/Polyfills/AssertObjectEqualsTest.php @@ -15,6 +15,7 @@ use stdClass; use TypeError; use Yoast\PHPUnitPolyfills\Exceptions\InvalidComparisonMethodException; +use Yoast\PHPUnitPolyfills\Helpers\ComparatorValidator; use Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals; use Yoast\PHPUnitPolyfills\Polyfills\ExpectExceptionMessageMatches; use Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ChildValueObject; @@ -28,8 +29,10 @@ * Availability test for the function polyfilled by the AssertObjectEquals trait. * * @covers \Yoast\PHPUnitPolyfills\Polyfills\AssertObjectEquals + * @covers \Yoast\PHPUnitPolyfills\Helpers\ComparatorValidator */ #[CoversClass( AssertObjectEquals::class )] +#[CoversClass( ComparatorValidator::class )] final class AssertObjectEqualsTest extends TestCase { use AssertObjectEquals; diff --git a/tests/Polyfills/AssertObjectNotEqualsTest.php b/tests/Polyfills/AssertObjectNotEqualsTest.php new file mode 100644 index 0000000..ec32be5 --- /dev/null +++ b/tests/Polyfills/AssertObjectNotEqualsTest.php @@ -0,0 +1,505 @@ +assertObjectNotEquals( $expected, $actual ); + } + + /** + * Verify behaviour when passing the $method parameter. + * + * @return void + */ + public function testAssertObjectNotEqualsCustomMethodName() { + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'nonDefaultName' ); + } + + /** + * Verify behaviour when $expected is a child of $actual. + * + * @return void + */ + public function testAssertObjectNotEqualsExpectedChildOfActual() { + $expected = new ChildValueObject( 'inheritance' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual ); + } + + /** + * Verify behaviour when $actual is a child of $expected. + * + * @return void + */ + public function testAssertObjectNotEqualsActualChildOfExpected() { + $expected = new ValueObject( 'inheritance' ); + $actual = new ChildValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $expected parameter is not an object. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnExpectedNotObject() { + $this->expectException( TypeError::class ); + + if ( \PHP_VERSION_ID >= 80000 + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + $msg = 'assertObjectNotEquals(): Argument #1 ($expected) must be of type object, string given'; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 7 or PHP 8 with the polyfill. + $pattern = '`^Argument 1 passed to [^\s]*assertObjectNotEquals\(\) must be an object, string given`'; + $this->expectExceptionMessageMatches( $pattern ); + } + + $actual = new ValueObject( 'test' ); + $this->assertObjectNotEquals( 'className', $actual ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $actual parameter is not an object. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnActualNotObject() { + $this->expectException( TypeError::class ); + + if ( \PHP_VERSION_ID >= 80000 + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + $msg = 'assertObjectNotEquals(): Argument #2 ($actual) must be of type object, string given'; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 7. + $pattern = '`^Argument 2 passed to [^\s]*assertObjectNotEquals\(\) must be an object, string given`'; + $this->expectExceptionMessageMatches( $pattern ); + } + + $expected = new ValueObject( 'test' ); + $this->assertObjectNotEquals( $expected, 'className' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $method parameter is not + * juggleable to a string. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMethodNotJuggleableToString() { + $this->expectException( TypeError::class ); + + if ( \PHP_VERSION_ID >= 80000 + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + $msg = 'assertObjectNotEquals(): Argument #3 ($method) must be of type string, array given'; + $this->expectExceptionMessage( $msg ); + } + else { + // PHP 7. + $pattern = '`^Argument 3 passed to [^\s]*assertObjectNotEquals\(\) must be of the type string, array given`'; + $this->expectExceptionMessageMatches( $pattern ); + } + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, [] ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $actual object + * does not contain a method called $method. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMethodNotDeclared() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::doesNotExist() does not exist.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotExistException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotExistException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'doesNotExist' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when no return type is declared. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMissingReturnType() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsMissingReturnType() does not declare bool return type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareBoolReturnTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareBoolReturnTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 100 ); + $actual = new ValueObject( 101 ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsMissingReturnType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the declared return type in a union, intersection or DNF type. + * + * @requires PHP 8.0 + * + * @return void + */ + #[RequiresPhp( '8.0' )] + public function testAssertObjectNotEqualsFailsOnNonNamedTypeReturnType() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectUnionReturnType::equalsUnionReturnType() does not declare bool return type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareBoolReturnTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareBoolReturnTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectUnionReturnType( 100 ); + $actual = new ValueObjectUnionReturnType( 010 ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsUnionReturnType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the declared return type is nullable. + * + * @requires PHP 7.1 + * + * @return void + */ + #[RequiresPhp( '7.1' )] + public function testAssertObjectNotEqualsFailsOnNullableReturnType() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectNullableReturnType::equalsNullableReturnType() does not declare bool return type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareBoolReturnTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareBoolReturnTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectNullableReturnType( 100 ); + $actual = new ValueObjectNullableReturnType( 250 ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsNullableReturnType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the declared return type is not boolean. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnNonBooleanReturnType() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsNonBooleanReturnType() does not declare bool return type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareBoolReturnTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareBoolReturnTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 100 ); + $actual = new ValueObject( 123 ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsNonBooleanReturnType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $method accepts more than one parameter. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMethodAllowsForMoreParams() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsTwoParams() does not declare exactly one parameter.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareExactlyOneParameterException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareExactlyOneParameterException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsTwoParams' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $method is not a required parameter. + * + * @requires PHP 7.1 + * + * @return void + */ + #[RequiresPhp( '7.1' )] + public function testAssertObjectNotEqualsFailsOnMethodParamNotRequired() { + $msg = 'Comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectParamNotRequired::equalsParamNotRequired() does not declare exactly one parameter.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareExactlyOneParameterException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareExactlyOneParameterException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectParamNotRequired( 'test' ); + $actual = new ValueObjectParamNotRequired( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsParamNotRequired' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $method parameter + * does not have a type declaration. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMethodParamMissingTypeDeclaration() { + $msg = 'Parameter of comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsParamNoType() does not have a declared type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareParameterTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareParameterTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsParamNoType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $method parameter + * has a PHP 8.0+ union type declaration. + * + * @requires PHP 8.0 + * + * @return void + */ + #[RequiresPhp( '8.0' )] + public function testAssertObjectNotEqualsFailsOnMethodParamHasUnionTypeDeclaration() { + $msg = 'Parameter of comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObjectUnion::equalsParamUnionType() does not have a declared type.'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotDeclareParameterTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotDeclareParameterTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObjectUnion( 'test' ); + $actual = new ValueObjectUnion( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsParamUnionType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $method parameter + * does not have a class-based type declaration. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMethodParamNonClassTypeDeclaration() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsParamNonClassType().'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotAcceptParameterTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotAcceptParameterTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsParamNonClassType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when the $method parameter + * has a class-based type declaration, but for a class which doesn't exist. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMethodParamNonExistentClassTypeDeclaration() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equalsParamNonExistentClassType().'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotAcceptParameterTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotAcceptParameterTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual, 'equalsParamNonExistentClassType' ); + } + + /** + * Verify that the assertObjectNotEquals() method throws an error when $expected is not + * an instance of the type declared for the $method parameter. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnMethodParamTypeMismatch() { + $msg = 'is not an accepted argument type for comparison method Yoast\PHPUnitPolyfills\Tests\Polyfills\Fixtures\ValueObject::equals().'; + + $exception = self::COMPARATOR_EXCEPTION; + if ( \class_exists( ComparisonMethodDoesNotAcceptParameterTypeException::class ) + && \version_compare( PHPUnit_Version::id(), '11.2.0', '>=' ) + ) { + // PHPUnit >= 11.2: PHPUnit native assertion uses the PHPUnit exceptions. + $exception = ComparisonMethodDoesNotAcceptParameterTypeException::class; + } + + $this->expectException( $exception ); + $this->expectExceptionMessage( $msg ); + + $actual = new ValueObject( 'test' ); + $this->assertObjectNotEquals( new stdClass(), $actual ); + } + + /** + * Verify that the assertObjectNotEquals() method fails a test when a call to method + * determines that the objects are not equal. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsOnEqual() { + $msg = 'Failed asserting that two objects are not equal.'; + + $this->expectException( AssertionFailedError::class ); + $this->expectExceptionMessage( $msg ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectNotEquals( $expected, $actual ); + } + + /** + * Verify that the assertObjectNotEquals() method fails a test with a custom failure message, when a call + * to the method determines that the objects are not equal and the custom $message parameter has been passed. + * + * @return void + */ + public function testAssertObjectNotEqualsFailsAsNotEqualWithCustomMessage() { + $pattern = '`^This assertion failed for reason XYZ\s+Failed asserting that two objects are not equal\.`'; + + $this->expectException( AssertionFailedError::class ); + $this->expectExceptionMessageMatches( $pattern ); + + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'test' ); + $this->assertObjectNotEquals( $expected, $actual, 'equals', 'This assertion failed for reason XYZ' ); + } +} diff --git a/tests/TestCases/TestCaseTestTrait.php b/tests/TestCases/TestCaseTestTrait.php index 3f51cc5..34c935a 100644 --- a/tests/TestCases/TestCaseTestTrait.php +++ b/tests/TestCases/TestCaseTestTrait.php @@ -166,4 +166,15 @@ final public function testAvailabilityAssertArrayWithListKeys() { self::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys( $expected, $actual, [ 'a' ] ); } + + /** + * Verify availability of trait polyfilled PHPUnit methods [19]. + * + * @return void + */ + final public function testAvailabilityAssertObjectNotEquals() { + $expected = new ValueObject( 'test' ); + $actual = new ValueObject( 'different' ); + $this->assertObjectNotEquals( $expected, $actual ); + } }