Skip to content

Commit

Permalink
Closes #5478
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Aug 19, 2023
1 parent 53e630f commit adc23ac
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 2 deletions.
17 changes: 17 additions & 0 deletions .psalm/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@
static::objectEquals($expected, $method),
$message,
)</code>
<code>static::assertThat(
$object,
new LogicalNot(
new ObjectHasProperty($propertyName),
),
$message,
)</code>
<code>static::assertThat(
$object,
new ObjectHasProperty($propertyName),
$message,
)</code>
<code>static::assertThat($haystack, $constraint, $message)</code>
<code>static::assertThat($haystack, $constraint, $message)</code>
<code>static::assertThat($haystack, $constraint, $message)</code>
Expand Down Expand Up @@ -437,6 +449,11 @@
<code>hasProperty</code>
</MissingThrowsDocblock>
</file>
<file src="src/Framework/Constraint/Object/ObjectHasProperty.php">
<MissingThrowsDocblock>
<code>hasProperty</code>
</MissingThrowsDocblock>
</file>
<file src="src/Framework/Constraint/Operator/BinaryOperator.php">
<UnsafeInstantiation>
<code>new static</code>
Expand Down
7 changes: 7 additions & 0 deletions ChangeLog-9.6.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes of the PHPUnit 9.6 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.

## [9.6.11] - 2023-MM-DD

### Added

* [#5478](https://github.com/sebastianbergmann/phpunit/pull/5478): `assertObjectHasProperty()` and `assertObjectNotHasProperty()`

## [9.6.10] - 2023-07-10

### Changed
Expand Down Expand Up @@ -77,6 +83,7 @@ All notable changes of the PHPUnit 9.6 release series are documented in this fil
* [#5064](https://github.com/sebastianbergmann/phpunit/issues/5064): Deprecate `PHPUnit\Framework\TestCase::getMockClass()`
* [#5132](https://github.com/sebastianbergmann/phpunit/issues/5132): Deprecate `Test` suffix for abstract test case classes

[9.6.11]: https://github.com/sebastianbergmann/phpunit/compare/9.6.10...9.6
[9.6.10]: https://github.com/sebastianbergmann/phpunit/compare/9.6.9...9.6.10
[9.6.9]: https://github.com/sebastianbergmann/phpunit/compare/9.6.8...9.6.9
[9.6.8]: https://github.com/sebastianbergmann/phpunit/compare/9.6.7...9.6.8
Expand Down
35 changes: 33 additions & 2 deletions src/Framework/Assert.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
use PHPUnit\Framework\Constraint\LogicalXor;
use PHPUnit\Framework\Constraint\ObjectEquals;
use PHPUnit\Framework\Constraint\ObjectHasAttribute;
use PHPUnit\Framework\Constraint\ObjectHasProperty;
use PHPUnit\Framework\Constraint\RegularExpression;
use PHPUnit\Framework\Constraint\SameSize;
use PHPUnit\Framework\Constraint\StringContains;
Expand Down Expand Up @@ -1304,7 +1305,7 @@ public static function assertClassNotHasStaticAttribute(string $attributeName, s
*/
public static function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void
{
self::createWarning('assertObjectHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectHasProperty() (PHPUnit 10.1.0+) instead.');
self::createWarning('assertObjectHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectHasProperty() instead.');

if (!self::isValidObjectAttributeName($attributeName)) {
throw InvalidArgumentException::create(1, 'valid attribute name');
Expand Down Expand Up @@ -1334,7 +1335,7 @@ public static function assertObjectHasAttribute(string $attributeName, $object,
*/
public static function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void
{
self::createWarning('assertObjectNotHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectNotHasProperty() (PHPUnit 10.1.0+) instead.');
self::createWarning('assertObjectNotHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectNotHasProperty() instead.');

if (!self::isValidObjectAttributeName($attributeName)) {
throw InvalidArgumentException::create(1, 'valid attribute name');
Expand All @@ -1353,6 +1354,36 @@ public static function assertObjectNotHasAttribute(string $attributeName, $objec
);
}

/**
* Asserts that an object has a specified property.
*
* @throws ExpectationFailedException
*/
final public static function assertObjectHasProperty(string $propertyName, object $object, string $message = ''): void
{
static::assertThat(
$object,
new ObjectHasProperty($propertyName),
$message,
);
}

/**
* Asserts that an object does not have a specified property.
*
* @throws ExpectationFailedException
*/
final public static function assertObjectNotHasProperty(string $propertyName, object $object, string $message = ''): void
{
static::assertThat(
$object,
new LogicalNot(
new ObjectHasProperty($propertyName),
),
$message,
);
}

/**
* Asserts that two variables have the same type and value.
* Used on objects, it asserts that two variables reference
Expand Down
36 changes: 36 additions & 0 deletions src/Framework/Assert/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,42 @@ function assertObjectNotHasAttribute(string $attributeName, $object, string $mes
}
}

if (!function_exists('PHPUnit\Framework\assertObjectHasProperty')) {
/**
* Asserts that an object has a specified property.
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
* @throws Exception
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @see Assert::assertObjectHasProperty
*/
function assertObjectHasProperty(string $attributeName, object $object, string $message = ''): void
{
Assert::assertObjectHasProperty(...func_get_args());
}
}

if (!function_exists('PHPUnit\Framework\assertObjectNotHasProperty')) {
/**
* Asserts that an object does not have a specified property.
*
* @throws ExpectationFailedException
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
* @throws Exception
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @see Assert::assertObjectNotHasProperty
*/
function assertObjectNotHasProperty(string $attributeName, object $object, string $message = ''): void
{
Assert::assertObjectNotHasProperty(...func_get_args());
}
}

if (!function_exists('PHPUnit\Framework\assertSame')) {
/**
* Asserts that two variables have the same type and value.
Expand Down
84 changes: 84 additions & 0 deletions src/Framework/Constraint/Object/ObjectHasProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;

use function get_class;
use function gettype;
use function is_object;
use function sprintf;
use ReflectionObject;

/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*/
final class ObjectHasProperty extends Constraint
{
/**
* @var string
*/
private $propertyName;

public function __construct(string $propertyName)
{
$this->propertyName = $propertyName;
}

/**
* Returns a string representation of the constraint.
*/
public function toString(): string
{
return sprintf(
'has property "%s"',
$this->propertyName,
);
}

/**
* Evaluates the constraint for parameter $other. Returns true if the
* constraint is met, false otherwise.
*
* @param mixed $other value or object to evaluate
*/
protected function matches($other): bool
{
if (!is_object($other)) {
return false;
}

return (new ReflectionObject($other))->hasProperty($this->propertyName);
}

/**
* Returns the description of the failure.
*
* The beginning of failure messages is "Failed asserting that" in most
* cases. This method should return the second part of that sentence.
*
* @param mixed $other evaluated value or object
*/
protected function failureDescription($other): string
{
if (is_object($other)) {
return sprintf(
'object of class "%s" %s',
get_class($other),
$this->toString(),
);
}

return sprintf(
'"%s" (%s) %s',
$other,
gettype($other),
$this->toString(),
);
}
}
32 changes: 32 additions & 0 deletions tests/unit/Framework/AssertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2122,6 +2122,38 @@ public function testTwoObjectsCanBeAssertedToBeEqualUsingComparisonMethod(): voi
$this->fail();
}

public function testObjectHasPropertyCanBeAsserted(): void
{
$objectWithProperty = new stdClass;
$objectWithProperty->theProperty = 'value';

$this->assertObjectHasProperty('theProperty', $objectWithProperty);

try {
$this->assertObjectHasProperty('doesNotExist', $objectWithProperty);
} catch (AssertionFailedError $e) {
return;
}

$this->fail();
}

public function testObjectDoesNotHavePropertyCanBeAsserted(): void
{
$objectWithProperty = new stdClass;
$objectWithProperty->theProperty = 'value';

$this->assertObjectNotHasProperty('doesNotExist', $objectWithProperty);

try {
$this->assertObjectNotHasProperty('theProperty', $objectWithProperty);
} catch (AssertionFailedError $e) {
return;
}

$this->fail();
}

protected function sameValues(): array
{
$object = new SampleClass(4, 8, 15);
Expand Down
63 changes: 63 additions & 0 deletions tests/unit/Framework/Constraint/ObjectHasPropertyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\Constraint;

use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
use stdClass;

/**
* @covers \PHPUnit\Framework\Constraint\ObjectHasProperty
*
* @small
*/
final class ObjectHasPropertyTest extends TestCase
{
public function testCanBeEvaluated(): void
{
$constraint = new ObjectHasProperty('theProperty');

$objectWithProperty = new stdClass;
$objectWithProperty->theProperty = 'value';

$this->assertTrue($constraint->evaluate($objectWithProperty, '', true));
$this->assertFalse($constraint->evaluate(new stdClass, '', true));
$this->assertFalse($constraint->evaluate(null, '', true));

$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('Failed asserting that object of class "stdClass" has property "theProperty".');

$constraint->evaluate(new stdClass);
}

public function testHandlesNonObjectsGracefully(): void
{
$constraint = new ObjectHasProperty('theProperty');

$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('Failed asserting that "non-object" (string) has property "theProperty".');

$constraint->evaluate('non-object');
}

public function testCanBeRepresentedAsString(): void
{
$constraint = new ObjectHasProperty('theProperty');

$this->assertSame('has property "theProperty"', $constraint->toString());
}

public function testIsCountable(): void
{
$constraint = new ObjectHasProperty('theProperty');

$this->assertCount(1, $constraint);
}
}

0 comments on commit adc23ac

Please sign in to comment.