• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / PHPUnit-Polyfills / 10681432610

03 Sep 2024 10:31AM UTC coverage: 95.846% (-1.6%) from 97.405%
10681432610

push

github

jrfnl
Tests: add `Before/After[Class]` attributes

PHPUnit 10 introduced attributes as replacements for docblock annotations.
PHPUnit 11 deprecates the use of docblock annotations in favour of attributes.

If both an attribute as well as an annotation are found, no PHPUnit deprecation warning will be thrown.

This commit adds the `Before/After*` attributes in all the appropriate places.

The `@before/after*` annotations remain as the tests also still need to run on PHP 5.6 - 8.0 using PHPUnit 5.x - 9.x.
These can be removed once the codebase has a PHP 8.1/PHPUnit 10 minimum requirement.

Note: due to the syntax for attributes, these can be safely added as they are ignored as comments on PHP < 8.0.
Along the same line, if there is no "listener" for the attributes (PHP 8.0/PHPUnit 9.x), they are ignored by PHP as well.

2 of 2 new or added lines in 1 file covered. (100.0%)

7 existing lines in 1 file now uncovered.

623 of 650 relevant lines covered (95.85%)

136.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.55
/src/Polyfills/AssertObjectEquals.php
1
<?php
2

3
namespace Yoast\PHPUnitPolyfills\Polyfills;
4

5
use ReflectionClass;
6
use ReflectionException;
7
use ReflectionNamedType;
8
use ReflectionObject;
9
use ReflectionType;
10
use TypeError;
11
use Yoast\PHPUnitPolyfills\Exceptions\InvalidComparisonMethodException;
12

13
/**
14
 * Polyfill the Assert::assertObjectEquals() methods.
15
 *
16
 * Introduced in PHPUnit 9.4.0.
17
 *
18
 * The polyfill implementation closely matches the PHPUnit native implementation with the exception
19
 * of the return type check and the names of the thrown exceptions.
20
 *
21
 * @link https://github.com/sebastianbergmann/phpunit/issues/4467
22
 * @link https://github.com/sebastianbergmann/phpunit/issues/4707
23
 * @link https://github.com/sebastianbergmann/phpunit/commit/1dba8c3a4b2dd04a3ff1869f75daaeb6757a14ee
24
 * @link https://github.com/sebastianbergmann/phpunit/commit/6099c5eefccfda860c889f575d58b5fe6cc10c83
25
 */
26
trait AssertObjectEquals {
27

28
        /**
29
         * Asserts that two objects are considered equal based on a custom object comparison
30
         * using a comparator method in the target object.
31
         *
32
         * The custom comparator method is expected to have the following method
33
         * signature: `equals(self $other): bool` (or similar with a different method name).
34
         *
35
         * Basically, the assertion checks the following:
36
         * - A method with name $method must exist on the $actual object.
37
         * - The method must accept exactly one argument and this argument must be required.
38
         * - This parameter must have a classname-based declared type.
39
         * - The $expected object must be compatible with this declared type.
40
         * - The method must have a declared bool return type. (JRF: not verified in this implementation)
41
         * - `$actual->$method($expected)` returns boolean true.
42
         *
43
         * @param object $expected Expected value.
44
         * @param object $actual   The value to test.
45
         * @param string $method   The name of the comparator method within the object.
46
         * @param string $message  Optional failure message to display.
47
         *
48
         * @return void
49
         *
50
         * @throws TypeError                        When any of the passed arguments do not meet the required type.
51
         * @throws InvalidComparisonMethodException When the comparator method does not comply with the requirements.
52
         */
53
        final public static function assertObjectEquals( $expected, $actual, $method = 'equals', $message = '' ) {
329✔
54
                /*
55
                 * Parameter input validation.
56
                 * In PHPUnit this is done via PHP native type declarations. Emulating this for the polyfill.
57
                 */
58
                if ( \is_object( $expected ) === false ) {
329✔
59
                        throw new TypeError(
18✔
60
                                \sprintf(
18✔
61
                                        'Argument 1 passed to assertObjectEquals() must be an object, %s given',
18✔
62
                                        \gettype( $expected )
18✔
63
                                )
3✔
64
                        );
3✔
65
                }
66

67
                if ( \is_object( $actual ) === false ) {
311✔
68
                        throw new TypeError(
18✔
69
                                \sprintf(
18✔
70
                                        'Argument 2 passed to assertObjectEquals() must be an object, %s given',
18✔
71
                                        \gettype( $actual )
18✔
72
                                )
3✔
73
                        );
3✔
74
                }
75

76
                if ( \is_scalar( $method ) === false ) {
293✔
77
                        throw new TypeError(
18✔
78
                                \sprintf(
18✔
79
                                        'Argument 3 passed to assertObjectEquals() must be of the type string, %s given',
18✔
80
                                        \gettype( $method )
18✔
81
                                )
3✔
82
                        );
3✔
83
                }
84
                else {
85
                        $method = (string) $method;
275✔
86
                }
87

88
                /*
89
                 * Comparator method validation.
90
                 */
91
                $reflObject = new ReflectionObject( $actual );
275✔
92

93
                if ( $reflObject->hasMethod( $method ) === false ) {
275✔
94
                        throw new InvalidComparisonMethodException(
18✔
95
                                \sprintf(
18✔
96
                                        'Comparison method %s::%s() does not exist.',
18✔
97
                                        \get_class( $actual ),
18✔
98
                                        $method
18✔
99
                                )
3✔
100
                        );
3✔
101
                }
102

103
                $reflMethod = $reflObject->getMethod( $method );
257✔
104

105
                /*
106
                 * As the next step, PHPUnit natively would validate the return type,
107
                 * but as return type declarations is a PHP 7.0+ feature, the polyfill
108
                 * skips this check in favour of checking the type of the actual
109
                 * returned value.
110
                 *
111
                 * Also see the upstream discussion about this:
112
                 * {@link https://github.com/sebastianbergmann/phpunit/issues/4707}
113
                 */
114

115
                /*
116
                 * Comparator method parameter requirements validation.
117
                 */
118
                if ( $reflMethod->getNumberOfParameters() !== 1
257✔
119
                        || $reflMethod->getNumberOfRequiredParameters() !== 1
257✔
120
                ) {
121
                        throw new InvalidComparisonMethodException(
34✔
122
                                \sprintf(
34✔
123
                                        'Comparison method %s::%s() does not declare exactly one parameter.',
34✔
124
                                        \get_class( $actual ),
34✔
125
                                        $method
34✔
126
                                )
6✔
127
                        );
6✔
128
                }
129

130
                $noDeclaredTypeError = \sprintf(
223✔
131
                        'Parameter of comparison method %s::%s() does not have a declared type.',
223✔
132
                        \get_class( $actual ),
223✔
133
                        $method
223✔
134
                );
42✔
135

136
                $notAcceptableTypeError = \sprintf(
223✔
137
                        '%s is not an accepted argument type for comparison method %s::%s().',
223✔
138
                        \get_class( $expected ),
223✔
139
                        \get_class( $actual ),
223✔
140
                        $method
223✔
141
                );
42✔
142

143
                $reflParameter = $reflMethod->getParameters()[0];
223✔
144

145
                if ( \method_exists( $reflParameter, 'hasType' ) ) {
223✔
146
                        // PHP >= 7.0.
147
                        $hasType = $reflParameter->hasType();
223✔
148
                        if ( $hasType === false ) {
223✔
149
                                throw new InvalidComparisonMethodException( $noDeclaredTypeError );
18✔
150
                        }
151

152
                        $type = $reflParameter->getType();
205✔
153
                        if ( \class_exists( 'ReflectionNamedType' ) ) {
205✔
154
                                // PHP >= 7.1.
155
                                if ( ( $type instanceof ReflectionNamedType ) === false ) {
185✔
156
                                        throw new InvalidComparisonMethodException( $noDeclaredTypeError );
3✔
157
                                }
158

159
                                $typeName = $type->getName();
182✔
160
                        }
161
                        else {
162
                                /*
163
                                 * PHP 7.0.
164
                                 * Checking for `ReflectionType` will not throw an error on union types,
165
                                 * but then again union types are not supported on PHP 7.0.
166
                                 */
167
                                if ( ( $type instanceof ReflectionType ) === false ) {
20✔
UNCOV
168
                                        throw new InvalidComparisonMethodException( $noDeclaredTypeError );
×
169
                                }
170

171
                                $typeName = (string) $type;
190✔
172
                        }
173
                }
174
                else {
175
                        // PHP < 7.0.
176
                        try {
177
                                /*
178
                                 * Using `ReflectionParameter::getClass()` will trigger an autoload of the class,
179
                                 * but that's okay as for a valid class type that would be triggered on the
180
                                 * function call to the $method (at the end of this assertion) anyway.
181
                                 */
UNCOV
182
                                $hasType = $reflParameter->getClass();
×
UNCOV
183
                        } catch ( ReflectionException $e ) {
×
184
                                // Class with a type declaration for a non-existent class.
UNCOV
185
                                throw new InvalidComparisonMethodException( $notAcceptableTypeError );
×
186
                        }
187

UNCOV
188
                        if ( ( $hasType instanceof ReflectionClass ) === false ) {
×
189
                                // Array or callable type.
UNCOV
190
                                throw new InvalidComparisonMethodException( $noDeclaredTypeError );
×
191
                        }
192

UNCOV
193
                        $typeName = $hasType->name;
×
194
                }
195

196
                /*
197
                 * Validate that the $expected object complies with the declared parameter type.
198
                 */
199
                if ( $typeName === 'self' ) {
202✔
200
                        $typeName = \get_class( $actual );
130✔
201
                }
202

203
                if ( ( $expected instanceof $typeName ) === false ) {
202✔
204
                        throw new InvalidComparisonMethodException( $notAcceptableTypeError );
54✔
205
                }
206

207
                /*
208
                 * Execute the comparator method.
209
                 */
210
                $result = $actual->{$method}( $expected );
148✔
211

212
                if ( \is_bool( $result ) === false ) {
148✔
213
                        throw new InvalidComparisonMethodException(
18✔
214
                                \sprintf(
18✔
215
                                        'Comparison method %s::%s() does not return a boolean value.',
18✔
216
                                        \get_class( $actual ),
18✔
217
                                        $method
18✔
218
                                )
3✔
219
                        );
3✔
220
                }
221

222
                $msg = \sprintf(
130✔
223
                        'Failed asserting that two objects are equal. The objects are not equal according to %s::%s()',
130✔
224
                        \get_class( $actual ),
130✔
225
                        $method
130✔
226
                );
24✔
227

228
                if ( $message !== '' ) {
130✔
229
                        $msg = $message . \PHP_EOL . $msg;
18✔
230
                }
231

232
                static::assertTrue( $result, $msg );
130✔
233
        }
76✔
234
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc