Upgrade PHPUnit to 11.x and reconcile with host unit-test tier (XWPENG-43) - #1974
Upgrade PHPUnit to 11.x and reconcile with host unit-test tier (XWPENG-43)#1974shadyvb wants to merge 3 commits into
Conversation
1481344 to
aeaf5d9
Compare
fd317ee to
8cf6476
Compare
There was a problem hiding this comment.
Pull request overview
Upgrades the test stack to PHPUnit 11/PHP 8.2 and modernizes integration-test discovery, mocks, and fixtures.
Changes:
- Migrates PHPUnit configuration, data providers, and deprecated API shims.
- Renames and restructures integration tests for suffix-based discovery.
- Updates Docker, Composer, CI, and contributor tooling for PHP 8.2.
Review assessment:
- High: the split editor tests can leak hooks because their teardown omits the parent teardown.
- Medium: three settings tests no longer reject duplicate audit logs.
Reviewed changes
Copilot reviewed 79 out of 81 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/lint-and-test.yml |
Uses PHP 8.2 in CI. |
.gitignore |
Ignores PHPUnit 11 cache. |
composer.json |
Upgrades PHPUnit and platform requirements. |
contributing.md |
Documents PHP 8.2 testing requirements. |
docker-compose.build.yml |
Updates PHP 8.2 image arguments. |
local/docker/wordpress/Dockerfile |
Makes PHP 8.2/Xdebug 3.3 defaults. |
package.json |
Adds explicit PHP 7.4 build arguments. |
phpunit-multisite.xml |
Migrates multisite configuration to PHPUnit 11. |
phpunit-unit.xml |
Migrates unit-suite configuration and discovery. |
phpunit.xml |
Migrates integration configuration and discovery. |
tests/bootstrap.php |
Loads renamed test base classes. |
tests/phpcs.xml.dist |
Permits new test filenames. |
tests/testcase.php |
Adds Mercator recovery and deprecation shim. |
tests/phpunit/Abilities_Test.php |
Renames the test class. |
tests/phpunit/Ability_Test.php |
Renames the test class. |
tests/phpunit/Admin_Cron_Purge_Test.php |
Renames the test class and references. |
tests/phpunit/Admin_Test.php |
Renames the test class. |
tests/phpunit/Alert_Test.php |
Renames the test class. |
tests/phpunit/Alert_Trigger_TestCase.php |
Renames the abstract alert base. |
tests/phpunit/Alerts_List_Test.php |
Renames the test class. |
tests/phpunit/Alerts_Test.php |
Renames the test class. |
tests/phpunit/Author_Test.php |
Renames the test class. |
tests/phpunit/CLI_Test.php |
Replaces an anonymous filter callback. |
tests/phpunit/Connector_Test.php |
Renames the test class. |
tests/phpunit/Connectors_Test.php |
Renames the test class. |
tests/phpunit/Cron_Scheduler_Test.php |
Renames the test class. |
tests/phpunit/DB_Driver_WPDB_Test.php |
Renames the test class. |
tests/phpunit/DB_Test.php |
Renames the test class. |
tests/phpunit/Date_Interval_Test.php |
Renames the test class. |
tests/phpunit/Export_Test.php |
Renames the test class. |
tests/phpunit/Exporter_CSV_Test.php |
Renames the test class. |
tests/phpunit/Exporter_JSON_Test.php |
Renames the test class. |
tests/phpunit/Filter_Input_Test.php |
Uses current exception and error-handler APIs. |
tests/phpunit/Functions_Test.php |
Renames the test class. |
tests/phpunit/Live_Update_Test.php |
Renames the test class. |
tests/phpunit/Log_Test.php |
Renames the test class. |
tests/phpunit/Network_Test.php |
Renames the test class and documentation. |
tests/phpunit/Plugin_Test.php |
Renames the test class. |
tests/phpunit/Scheduler_Handoff_Test.php |
Renames the test class. |
tests/phpunit/Scheduler_Selection_Test.php |
Renames the test class. |
tests/phpunit/fake-ability.php |
Updates the referenced test name. |
tests/phpunit/abilities/Abilities_REST_Integration_Test.php |
Renames the class and updates expected-usage handling. |
tests/phpunit/abilities/Ability_Create_Alert_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Create_Exclusion_Rule_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Delete_Alert_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Get_Alerts_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Get_Connectors_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Get_Exclusion_Rules_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Get_Record_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Get_Records_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Get_Settings_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Purge_Records_Test.php |
Renames the test class. |
tests/phpunit/abilities/Ability_Update_Settings_Test.php |
Renames the class and reference. |
tests/phpunit/alerts/Alert_Trigger_Action_Test.php |
Uses the renamed alert base class. |
tests/phpunit/alerts/Alert_Trigger_Author_Test.php |
Uses the renamed alert base class. |
tests/phpunit/alerts/Alert_Trigger_Context_Test.php |
Uses the renamed alert base class. |
tests/phpunit/connectors/Connector_ACF_Test.php |
Modernizes ordered ACF log assertions. |
tests/phpunit/connectors/Connector_Blogs_Test.php |
Splits multisite connector scenarios. |
tests/phpunit/connectors/Connector_Comments_Test.php |
Splits comment and reply scenarios. |
tests/phpunit/connectors/Connector_EDD_Test.php |
Splits EDD logging scenarios. |
tests/phpunit/connectors/Connector_Editor_Test.php |
Splits theme and plugin editor tests. |
tests/phpunit/connectors/Connector_GravityForms_Test.php |
Recreates tests with PHPUnit 11 callbacks. |
tests/phpunit/connectors/Connector_Installer_Test.php |
Replaces consecutive-call expectations. |
tests/phpunit/connectors/Connector_Jetpack_Test.php |
Recreates and splits Jetpack tests. |
tests/phpunit/connectors/Connector_Media_Test.php |
Splits attachment-parent scenarios. |
tests/phpunit/connectors/Connector_Menus_Test.php |
Splits menu assignment scenarios. |
tests/phpunit/connectors/Connector_Mercator_Test.php |
Modernizes ordered Mercator assertions. |
tests/phpunit/connectors/Connector_Posts_Test.php |
Recreates and splits post-transition tests. |
tests/phpunit/connectors/Connector_Settings_Test.php |
Splits settings and redaction scenarios. |
tests/phpunit/connectors/Connector_Taxonomies_Test.php |
Modernizes taxonomy context assertions. |
tests/phpunit/connectors/Connector_Two_Factor_Test.php |
Renames the test class. |
tests/phpunit/connectors/Connector_User_Switching_Test.php |
Updates class and mock APIs. |
tests/phpunit/connectors/Connector_Users_Test.php |
Splits and modernizes user log assertions. |
tests/phpunit/connectors/Connector_WordPress_SEO_Test.php |
Renames the test class. |
tests/phpunit/connectors/test-class-connector-blogs.php |
Removes the legacy Blogs test file. |
tests/phpunit/connectors/test-class-connector-gravityforms.php |
Removes the legacy Gravity Forms test file. |
tests/phpunit/connectors/test-class-connector-jetpack.php |
Removes the legacy Jetpack test file. |
tests/phpunit/connectors/test-class-connector-posts.php |
Removes the legacy Posts test file. |
tests/phpunit/unit/Connectors_Unit_Test.php |
Adds PHPUnit 11 data-provider attributes. |
tests/phpunit/unit/Query_Unit_Test.php |
Adds PHPUnit 11 data-provider attributes. |
Suppressed comments (4)
tests/phpunit/connectors/Connector_Editor_Test.php:89
- Splitting this into a second test leaves the first test's mocked connector registered: this class's
tearDown()restores the fixture file but never callsparent::tearDown(), so WordPress's hook snapshot is not restored. The second AJAX action can therefore invoke the previous mock's already-satisfiedonce()expectation and fail depending on execution order. Update the existing teardown to callparent::tearDown()after restoring the file.
tests/phpunit/connectors/Connector_Settings_Test.php:107 - The original combined test required exactly one log per option update;
atLeastOnce()now allows duplicate audit records to pass unnoticed. Preserve that contract withonce()for this single update.
This issue also appears in the following locations of the same file:
- line 141
- line 175
tests/phpunit/connectors/Connector_Settings_Test.php:141
- The original combined test required exactly one log per option update;
atLeastOnce()now allows duplicate audit records to pass unnoticed. Preserve that contract withonce()for this single update.
tests/phpunit/connectors/Connector_Settings_Test.php:175 - The original combined test required exactly one log per option update;
atLeastOnce()now allows duplicate audit records to pass unnoticed. Preserve that contract withonce()for this single update.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…WPENG-43). Migrate discovery, configs, and connector tests to PHPUnit 11; remove Mock_Call_Recorder in favor of split once()->with() expectations; apply parity fixes for ACF save_post, Mercator make_primary(), and Users get_password_reset_key() multi-log scenarios via per-class static record_log_call helpers; fix multisite Mercator test pollution and strengthen log context assertions.
Align array and call indentation via PHPCBF, fix Yoda conditions, and correct assignment alignment for lint-tests.
Keep the connection-check coverage from #1966 discoverable after merging develop, and use a named filter callback instead of a closure.
8cf6476 to
19d9307
Compare
bartoszgadomski
left a comment
There was a problem hiding this comment.
@shadyvb Thank you for working on this PR! Please check AI-assisted inline comments below.
| @@ -44,61 +42,36 @@ public function tearDown(): void { | |||
| file_put_contents( WP_PLUGIN_DIR . '/hello.php', $this->original_contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents | |||
There was a problem hiding this comment.
tearDown() overrides the polyfills' tearDown() but never calls parent::tearDown(), so WP_Ajax_UnitTestCase::tear_down() and WP_UnitTestCase_Base::tear_down() never run for this class. For both tests here that means:
- no
ROLLBACK— the next test'sstart_transaction()issuesSTART TRANSACTION, which implicitly commits whatever these tests wrote (the admin user, for one); _restore_hooks()is skipped, so the mocked connector stays registered onwp_ajax_edit-theme-plugin-file;wp_set_current_user( 0 ), the$_POST/$_GETreset andset_current_screen( 'front' )are all skipped;error_reporting()keeps the& ~E_WARNINGmask thatWP_Ajax_UnitTestCase::set_up()applies. Every later test saves that already-masked value in its ownset_up()and restores it, so the mask sticks for the rest of the process.
It does pass today — PHPUnit drops the invocation mocker after each test in __phpunit_verify(), so the leftover mock's log() is an inert stub rather than a second once() violation — and the missing call predates this PR. But the class now has two tests sharing that state, so this is a good moment to fix it:
| file_put_contents( WP_PLUGIN_DIR . '/hello.php', $this->original_contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents | |
| file_put_contents( WP_PLUGIN_DIR . '/hello.php', $this->original_contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents | |
| parent::tearDown(); |
| call_user_func( $add_method, 'permalink_structure', '' ); | ||
| $this->simulate_customize_save(); | ||
|
|
||
| $this->mock->expects( $this->atLeastOnce() ) |
There was a problem hiding this comment.
This test and the category_base / tag_base ones (lines 141 and 175) relax the count to atLeastOnce(), while test_callback_updated_option_users_can_register keeps once(). The combined test these were split from asserted exactly( 4 ) across four option writes, i.e. exactly one record per option, so once() should still hold here and would keep catching duplicate audit records.
If a single permalink/category/tag write genuinely logs more than once (a rewrite flush writing the option twice, say), a one-line comment saying so would help. Otherwise once() preserves the original contract.
| convertWarningsToExceptions="true" | ||
| xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" | ||
| cacheDirectory=".phpunit.cache" | ||
| failOnWarning="false" |
There was a problem hiding this comment.
The same run reports PHPUnit Deprecations: 87 and nothing surfaces them. displayDetailsOnPhpunitDeprecations="true" would make them visible. Most are probably the remaining doc-comment metadata — tests/ still has 23 @group and 7 @requires — and annotation metadata is removed in PHPUnit 12, so this is the tail of the same migration. The 7 @requires PHPUnit 5.7 are stale either way and can simply be deleted.
| * @link https://core.trac.wordpress.org/ticket/62004 | ||
| * @return void | ||
| */ | ||
| public function expectDeprecated(): void { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid |
There was a problem hiding this comment.
The shim looks right and the docblock is honest about omitting annotation parsing. The residual risk is that it fails silently: @expectedDeprecated / @expectedIncorrectUsage on a future test will now be ignored rather than erroring, and the next person has no way to notice the annotation is inert.
Worth a line in contributing.md (or here) pointing at setExpectedDeprecated() / setExpectedIncorrectUsage() as the only supported form.
| $this->assertFalse( 0 === did_action( $this->action_prefix . 'callback_user_register' ) ); | ||
| } | ||
|
|
||
| public function test_callback_user_register_by_logged_in_user() { |
There was a problem hiding this comment.
This test creates TestGuy before expects() is configured, so the first New user registration call reaches the mock while it still has no matcher and only the second create is counted. That is what makes once() correct here, but the ordering is load-bearing and unmarked — moving expects() above the first create() breaks the test for a non-obvious reason.
A short comment noting that the first registration is intentionally unobserved would do it, or set the current user in setUp() so the test body only performs the observed create.
| * @param mixed ...$args Log method arguments. | ||
| * @return void | ||
| */ | ||
| public static function record_log_call( ...$args ) { |
There was a problem hiding this comment.
$recorded_log_calls and record_log_call() are duplicated verbatim in Connector_Users_Test and Connector_Mercator_Test. Since WP_StreamTestCase already gained shared helpers in this PR, a protected static recorder there (with the reset in its setUp()) would remove three copies and give the ordered-capture pattern one documented home.
| * | ||
| * @var array | ||
| */ | ||
| private static $expected_post_context = array(); |
There was a problem hiding this comment.
Nit: this property is declared after all the test methods, ~600 lines below the top of the class (same pattern in Connector_Settings_Test line 283 and in Connector_Media_Test), whereas the rest of the suite declares properties at the top with a docblock.
It's also not reset in setUp(), so a test that forgets to assign it would validate against the previous test's expectations instead of failing. Resetting it in setUp() would turn that into a hard failure.
Fixes XWPENG-43.
This PR upgrades the test toolchain to PHPUnit 11 and aligns it with the host unit suite introduced in XWPENG-42. Production refactors from XWPENG-42 are not part of the XWPENG-43 commit—they are already on this branch as prior commits (see below). Together, XWPENG-42 + XWPENG-43 form the combined stack on
ticket/XWPENG-43-phpunit-11.Summary
expectDeprecated()onWP_StreamTestCase(PHPUnit 11 removed it; trait restores deprecated-expectation behavior where needed).phpunit-unit.xmlon PHPUnit 11 schema;Connectors_Unit_Test/Query_Unit_Testupdated with#[DataProvider]attributes.phpunit.xml/ integration configs usesuffix="Test.php"; ~58 integration test files renamed; removedWP_Stream_prefix from class names (ability tests live in namespaceWP_Stream, e.g.Ability_Create_Alert_Test).Mock_Call_Recorder; expectations use splitonce()->with()(and named staticrecord_log_callcallbacks where multiple log entries must be matched in order).save_postexpectations; Mercatormake_primary(); Usersget_password_reset_key()multi-log scenarios via per-class staticrecord_log_callhelpers.ensure_mercator_mapping_table()in testcase—Admin ajax tests left stalemercator.db.versionwithout creating the mapping table, polluting Mercator connector tests.wp_stream_preload_coverage_sources()andtests/phpunit/class-mock-call-recorder.php.config.platform8.2, Docker PHP 8.2 default,switch-to:php7.4build args,contributing.mdcorrections.Filter_Input_Test:expectException()+#[WithoutErrorHandler]on the regex validation test.Commits on this PR (
develop..HEAD)a5dbc77dc3c62d49aeaf5d98NOT in the XWPENG-43 commit: Query/Connectors production refactors above—they ship on the same branch because the branch was built on top of XWPENG-42 work.
Breaking / acceptance criteria notes
config.platform8.2; plugin Requires PHP remains 7.2 (tracked separately as XWPENG-46).failOnWarning/failOnNotice; scopedWithoutErrorHandlerwhere WordPress emits expected noise.Gotchas
wp-test-utilscompatibility.@groupannotations remain on integration tests.Test plan
composer test-unit— 53 tests, exit 0npm run test— 53 unit + 423 single-site + 423 multisite integration, all exit 0composer lintand lint-tests passChecklist
contributing.md).Release Changelog
Release Checklist
masterbranch.readme.txt.stream.php.Stable taginreadme.txt.classes/class-plugin.php.