Skip to content

phpunit: Clean up tests in preparation for PHPUnit 12 #43215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: removed
Comment: Remove unneeded `error_log()` in test.


Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ public function test_resend_auth_mail_sends_mail_and_remembers_2fa_token_success

$result = $sut->resend_auth_email( $user->ID, $transient_data, $my_token );

error_log( print_r( $result, true ) );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops!


// Verify the mail was sent
$this->assertTrue( $result, 'Resending auth mail should return true as success indicator.' );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: fixed
Comment: `->getMockBuilder()` no longer creates specifically-named classes. And we don't need one here, just an arbitrary object to be passed through.


12 changes: 9 additions & 3 deletions projects/packages/roles/tests/php/Roles_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ public function test_current_user_to_role_with_no_match() {
* Test translating an user to a role by role.
*/
public function test_user_to_role_with_role() {
$user_mock = $this->getMockBuilder( 'WP_User' )->getMock();
$user_mock = (object) array();
'@phan-var \WP_User $user_mock'; // Not really, but it doesn't matter.

Functions\when( 'user_can' )->alias(
function ( $user, $cap ) use ( $user_mock ) {
return $user_mock === $user && 'administrator' === $cap;
Expand All @@ -106,7 +108,9 @@ function ( $user, $cap ) use ( $user_mock ) {
* Test translating an user to a role by capablity.
*/
public function test_user_to_role_with_capability() {
$user_mock = $this->getMockBuilder( 'WP_User' )->getMock();
$user_mock = (object) array();
'@phan-var \WP_User $user_mock'; // Not really, but it doesn't matter.

Functions\when( 'user_can' )->alias(
function ( $user, $cap ) use ( $user_mock ) {
return $user_mock === $user && 'edit_others_posts' === $cap;
Expand All @@ -120,7 +124,9 @@ function ( $user, $cap ) use ( $user_mock ) {
* Test translating an user to a role with no match.
*/
public function test_user_to_role_with_no_match() {
$user_mock = $this->getMockBuilder( 'WP_User' )->getMock();
$user_mock = (object) array();
'@phan-var \WP_User $user_mock'; // Not really, but it doesn't matter.

Functions\when( 'user_can' )->justReturn( false );

$this->assertFalse( $this->roles->translate_user_to_role( $user_mock ) );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: added
Comment: Update `WP_UnitTestCase_Fix` with a simple annotation parser for `@expectedDeprecated` and `@expectedIncorrectUsage`.


99 changes: 93 additions & 6 deletions projects/plugins/crm/tests/php/WP_UnitTestCase_Fix.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,118 @@
*/

// phpcs:disable Generic.Classes.DuplicateClassName.Found, Generic.Files.OneObjectStructurePerFile.MultipleFound
// phpcs:disable WordPress.NamingConventions.ValidFunctionName, WordPress.NamingConventions.ValidVariableName

namespace Automattic\Jetpack\PHPUnit;

use PHPUnit\Metadata\Annotation\Parser\Registry as AnnotationRegistry;
use ReflectionClass;
use ReflectionFunctionAbstract;
use ReflectionMethod;

if ( explode( '.', \PHPUnit\Runner\Version::id() )[0] >= 10 ) {
if ( ! class_exists( AnnotationRegistry::class ) ) {

/**
* Doc block annotation extraction for getAnnotations in PHPUnit 12.
*
* Adapted from code in PHPUnit 11 `PHPUnit\Metadata\Annotation\Parser\Registry` and `PHPUnit\Metadata\Annotation\Parser\DocBlock`.
*/
class WP_UnitTestCase_Fix_GetAnnotations {
/**
* Cache.
* @var array
*/
private static $cache;

/**
* Get annotations for a class method.
*
* @param string $className Class name.
* @param string $methodName Method name.
* @return array Annotation data.
*/
public static function getAnnotations( $className, $methodName ) {
if ( ! isset( self::$cache['class'][ $className ] ) ) {
self::$cache['class'][ $className ] = self::getForReflector( new ReflectionClass( $className ) );
}
if ( ! isset( self::$cache['method'][ $className ][ $methodName ] ) ) {
self::$cache['method'][ $className ][ $methodName ] = self::getForReflector( new ReflectionMethod( $className, $methodName ) );
}
return array(
'method' => self::$cache['method'][ $className ][ $methodName ],
'class' => self::$cache['class'][ $className ],
);
}

/**
* Extract annotations from a Reflection object.
*
* @param ReflectionClass|ReflectionFunctionAbstract $reflector Reflection object.
* @return array Annotation data.
*/
private static function getForReflector( $reflector ) {
$annotations = array();

if ( $reflector instanceof ReflectionClass ) {
$annotations = array_merge(
$annotations,
...array_map(
static function ( $trait ) {
return self::parseDocBlock( (string) $trait->getDocComment() ); },
array_values( $reflector->getTraits() )
)
);
}

return array_merge(
$annotations,
self::parseDocBlock( (string) $reflector->getDocComment() )
);
}

/**
* Extract annotations from a doc block comment.
*
* @param string $docblock Doc block.
* @return array Annotation data for `@expectedDeprecated` and `@expectedIncorrectUsage`.
*/
private static function parseDocBlock( $docblock ) {
$annotations = array();
$docblock = substr( (string) $docblock, 3, -2 );
if ( preg_match_all( '/@(?P<name>expectedDeprecated|expectedIncorrectUsage)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docblock, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $m ) {
$annotations[ $m['name'] ][] = $m['value'];
}
}
return $annotations;
}
}
}

trait WP_UnitTestCase_Fix {

/**
* For `WP_UnitTestCase::expectDeprecated()` to call.
*
* @return array Method and class annotations, at minimum `@expectedDeprecated` and `@expectedIncorrectUsage`.
*/
public function getAnnotations() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
return array(
'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(),
'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(),
);
public function getAnnotations() {
if ( class_exists( AnnotationRegistry::class ) ) {
return array(
'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(),
'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(),
);
}

return WP_UnitTestCase_Fix_GetAnnotations::getAnnotations( static::class, $this->name() );
}

/**
* Obsolete method where PHPUnit is mis-processing the doc comment to see a `@group` that doesn't exist.
* This redefinition hides the "bad" doc comment from PHPUnit.
*/
protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found, WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found
parent::checkRequirements();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: other
Comment: Update `WP_UnitTestCase_Fix` with a simple annotation parser for `@expectedDeprecated` and `@expectedIncorrectUsage`.


Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: other
Comment: `->getMockBuilder()` no longer creates specifically-named classes. And we don't really need a mock here anyway, just for some class by the name to exist.


Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: other
Comment: PHPUnit 12 makes `TestCase::__construct()` final. Use `set_up()` instead.


100 changes: 94 additions & 6 deletions projects/plugins/jetpack/tests/php/WP_UnitTestCase_Fix.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,119 @@
*/

// phpcs:disable Generic.Classes.DuplicateClassName.Found, Generic.Files.OneObjectStructurePerFile.MultipleFound
// phpcs:disable WordPress.NamingConventions.ValidFunctionName, WordPress.NamingConventions.ValidVariableName

namespace Automattic\Jetpack\PHPUnit;

use PHPUnit\Metadata\Annotation\Parser\Registry as AnnotationRegistry;
use ReflectionClass;
use ReflectionFunctionAbstract;
use ReflectionMethod;

if ( explode( '.', \PHPUnit\Runner\Version::id() )[0] >= 10 ) {
if ( ! class_exists( AnnotationRegistry::class ) ) {

/**
* Doc block annotation extraction for getAnnotations in PHPUnit 12.
*
* Adapted from code in PHPUnit 11 `PHPUnit\Metadata\Annotation\Parser\Registry` and `PHPUnit\Metadata\Annotation\Parser\DocBlock`.
*/
class WP_UnitTestCase_Fix_GetAnnotations {
/**
* Cache.
*
* @var array
*/
private static $cache;

/**
* Get annotations for a class method.
*
* @param string $className Class name.
* @param string $methodName Method name.
* @return array Annotation data.
*/
public static function getAnnotations( $className, $methodName ) {
if ( ! isset( self::$cache['class'][ $className ] ) ) {
self::$cache['class'][ $className ] = self::getForReflector( new ReflectionClass( $className ) );
}
if ( ! isset( self::$cache['method'][ $className ][ $methodName ] ) ) {
self::$cache['method'][ $className ][ $methodName ] = self::getForReflector( new ReflectionMethod( $className, $methodName ) );
}
return array(
'method' => self::$cache['method'][ $className ][ $methodName ],
'class' => self::$cache['class'][ $className ],
);
}

/**
* Extract annotations from a Reflection object.
*
* @param ReflectionClass|ReflectionFunctionAbstract $reflector Reflection object.
* @return array Annotation data.
*/
private static function getForReflector( $reflector ) {
$annotations = array();

if ( $reflector instanceof ReflectionClass ) {
$annotations = array_merge(
$annotations,
...array_map(
static function ( $trait ) {
return self::parseDocBlock( (string) $trait->getDocComment() ); },
array_values( $reflector->getTraits() )
)
);
}

return array_merge(
$annotations,
self::parseDocBlock( (string) $reflector->getDocComment() )
);
}

/**
* Extract annotations from a doc block comment.
*
* @param string $docblock Doc block.
* @return array Annotation data for `@expectedDeprecated` and `@expectedIncorrectUsage`.
*/
private static function parseDocBlock( $docblock ) {
$annotations = array();
$docblock = substr( (string) $docblock, 3, -2 );
if ( preg_match_all( '/@(?P<name>expectedDeprecated|expectedIncorrectUsage)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docblock, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $m ) {
$annotations[ $m['name'] ][] = $m['value'];
}
}
return $annotations;
}
}
}

trait WP_UnitTestCase_Fix {

/**
* For `WP_UnitTestCase::expectDeprecated()` to call.
*
* @return array Method and class annotations, at minimum `@expectedDeprecated` and `@expectedIncorrectUsage`.
*/
public function getAnnotations() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
return array(
'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(),
'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(),
);
public function getAnnotations() {
if ( class_exists( AnnotationRegistry::class ) ) {
return array(
'method' => AnnotationRegistry::getInstance()->forMethod( static::class, $this->name() )->symbolAnnotations(),
'class' => AnnotationRegistry::getInstance()->forClassName( static::class )->symbolAnnotations(),
);
}

return WP_UnitTestCase_Fix_GetAnnotations::getAnnotations( static::class, $this->name() );
}

/**
* Obsolete method where PHPUnit is mis-processing the doc comment to see a `@group` that doesn't exist.
* This redefinition hides the "bad" doc comment from PHPUnit.
*/
protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found, WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
protected function checkRequirements() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found
parent::checkRequirements();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ class Jetpack_Display_Posts_Widget_Test extends WP_UnitTestCase {
private $inst;

/**
* Jetpack_Display_Posts_Widget_Test constructor.
* Setup function.
*/
public function __construct( ...$args ) {
parent::__construct( ...$args ); // @phan-suppress-current-line PhanParamTooFewUnpack - Should be ok.
public function set_up() {
parent::set_up();
$this->inst = new Jetpack_Display_Posts_Widget();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1300,8 +1300,8 @@ public function test_get_hosting_provider_by_known_class() {
$this->assertFalse( $functions->get_hosting_provider_by_known_class() );

// Fake that the class exists for the test.
// @phan-suppress-next-line PhanUndeclaredClassReference
$this->getMockBuilder( '\\WPaaS\\Plugin' )->getMock();
// @phan-suppress-next-line PhanUndeclaredClassReference -- We're defining it here, Phan. 🙄
class_alias( static::class, \WPaaS\Plugin::class );

$this->assertEquals( 'gd-managed-wp', $functions->get_hosting_provider_by_known_class() );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: added
Comment: Update `WP_UnitTestCase_Fix` with a simple annotation parser for `@expectedDeprecated` and `@expectedIncorrectUsage`.


Loading
Loading